I'm trying to add an extra input field if user clicks add input button and store those values as an object:
const {useState } = React;
const InviteUser = () => {
const [userDetails, setUserDetails] = useState([
{
email: "",
role: ""
}
]);
const handleCancel = () => {
setUserDetails([
{
email: "",
role: ""
}
]);
};
const handleChange = (e, index) => {
const { name, value } = e.target;
let newUserDetails = [...userDetails];
newUserDetails[index] = { ...userDetails[index], [name]: value };
setUserDetails({ newUserDetails });
};
const addInput = () => {
setUserDetails([...userDetails, { email: "", role: "" }]);
};
return (
userDetails.length >= 0 && (
<div>
{[...userDetails].map((el, index) => (
<form key={index + el} name={el}>
<div>
<input
type="email"
name="email"
required
onChange={(event) => handleChange(event, index)}
value={el.email}
style={{ marginTop: 17 }}
placeholder="Enter Email Address"
/>
</div>
<div>
<input
type="text"
name="roles"
required
value={el.role}
style={{ marginTop: 17 }}
placeholder="Role"
onChange={(event) => handleChange(event, index)}
/>
</div>
</form>
))}
<div>
<button label="Invite Another?" onClick={addInput}>
Add input
</button>
</div>
<div>
<button>Invite</button>
<button onClick={() => handleCancel()}>Cancel</button>
</div>
</div>
)
);
};
ReactDOM.render(
<InviteUser/>,
document.getElementById("react")
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="react"></div>
A state is an array of objects that holds email and role:
const [userDetails, setUserDetails] = useState([
{
email: "",
role: ""
}
]);
handleChange function which updates the state by pushing an object (of users inputs values from each individual row) into an array:
const handleChange = (e, index) => {
const { name, value } = e.target;
let newUserDetails = [...userDetails];
newUserDetails[index] = { ...userDetails[index], [name]: value };
setUserDetails({ newUserDetails });
};
and addInput function adds new input fields row:
const addInput = () => {
setUserDetails([...userDetails, { email: "", role: "" }]);
};
And return JSX:
return (
userDetails.length >= 0 && (
<InputsContainer>
{[...userDetails].map((el, index) => (
<form key={index + el} name={el} onSubmit={handleSubmit}>
<EmailContainer>
<input
type="email"
name="email"
required
onChange={(event) => handleChange(event, index)}
value={el.email}
style={{ marginTop: 17 }}
placeholder="Enter Email Address"
/>
</EmailContainer>
<RolesContainer>
<input
type="text"
name="roles"
required
value={el.role}
style={{ marginTop: 17 }}
placeholder="Role"
onChange={(event) => handleChange(event, index)}
/>
</RolesContainer>
</form>
))}
<InviteButtonContainer>
<button label="Invite Another?" onClick={addInput}>
Add input
</button>
</InviteButtonContainer>
<SubmitButtonsContainer>
<button onClick={(event) => handleSubmit(event)}>Invite</button>
<button onClick={() => handleCancel()}>Cancel</button>
</SubmitButtonsContainer>
</InputsContainer>
)
);
What am I doing wrong here?
You've got two problems:
In setUserDetails you return the array wrapped in an object, instead of just returning the array.
In the roles field you take the value from el.role insted of el.roles.
In addition, since you update the current state, based on a previous one, it's better to use functional updates.
const {useState } = React;
const createNewEntry = () => ({ email: "", role: "" });
const InviteUser = () => {
const [userDetails, setUserDetails] = useState(() => [createNewEntry()]);
const handleCancel = () => {
setUserDetails([createNewEntry()]);
};
const handleChange = (e, index) => {
const { name, value } = e.target;
setUserDetails(userDetails => {
const newUserDetails = [...userDetails];
newUserDetails[index] = { ...userDetails[index], [name]: value };
return newUserDetails;
});
};
const addInput = () => {
setUserDetails([...userDetails, createNewEntry()]);
};
return (
userDetails.length >= 0 && (
<div>
{[...userDetails].map((el, index) => (
<form key={index + el} name={el}>
<div>
<input
type="email"
name="email"
required
onChange={(event) => handleChange(event, index)}
value={el.email}
style={{ marginTop: 17 }}
placeholder="Enter Email Address"
/>
</div>
<div>
<input
type="text"
name="roles"
required
value={el.roles}
style={{ marginTop: 17 }}
placeholder="Role"
onChange={(event) => handleChange(event, index)}
/>
</div>
</form>
))}
<div>
<button label="Invite Another?" onClick={addInput}>
Add input
</button>
</div>
<div>
<button>Invite</button>
<button onClick={() => handleCancel()}>Cancel</button>
</div>
</div>
)
);
};
ReactDOM.render(
<InviteUser/>,
document.getElementById("react")
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="react"></div>
Related
Users can add more text fields of size, color, and stocks. If I'll add more sizes, the values for the color and stocks will duplicate from what was first entered.
Expected output:
1st Size : small
color: red, stocks: 10
color: green, stocks: 3
2nd Size: medium
color: white, stocks: 3
color: red, stocks: 6 the sizes field.
What it currently does is in the 2nd size, it will just duplicate whatever value was entered from the first size. How can I fix this? Thank you.
How can I combine the indexes of the colorList loop and sizeList loop to avoid the duplicates of the value of the textfields?
Link: https://codesandbox.io/s/form-2-add-more-size-ddqqo?file=/demo.js
import React, { useState, useEffect } from "react";
import Box from "#mui/material/Box";
import { TextField, Button } from "#mui/material";
export default function BasicSelect() {
const [productName, setProductName] = useState();
const [sizeList, setSizeList] = useState([{ size: "" }]);
const [colorList, setColorList] = useState([{ color: "", colorStocks: "" }]);
//sizes
const handleServiceChange = (e, index) => {
const { name, value } = e.target;
const list = [...sizeList];
list[index][name] = value;
setSizeList(list);
};
const handleServiceRemove = (index) => {
const list = [...sizeList];
list.splice(index, 1);
setSizeList(list);
};
const handleServiceAdd = () => {
setSizeList([...sizeList, { service: "" }]);
};
// color
const handleColorChange = (e, index) => {
const { name, value } = e.target;
const list = [...colorList];
list[index][name] = value;
setColorList(list);
// console.log(colorList);
};
const handleColorStocksChange = (e, index) => {
const { name, value } = e.target;
const list = [...colorList];
list[index][name] = value;
setColorList(list);
// console.log(colorList);
};
// const handleColorChange = (e, index) => {
// const { value } = e.target;
// const arr = [...colorList]; //Shallow copy the existing state
// arr[index].color = value; //Update the size to the selected size
// console.log(arr[index].value);
// setColorList([...arr]); //Set the updated array to be the new state
// };
// const handleColorStocksChange = (e, index) => {
// const { value } = e.target;
// console.log(value);
// const arr = [...colorList];
// arr[index].colorStocks = value;
// // console.log(arr)
// setColorList([...arr]);
// };
const handleColorRemove = (index) => {
const list = [...colorList];
list.splice(index, 1);
setColorList(list);
};
const handleColorAdd = () => {
setColorList([...colorList, { color: "", colorStocks: "" }]);
};
const handleSubmit = async (e) => {
e.preventDefault();
console.log("Product: ", productName, "size: ", sizeList, colorList);
};
return (
<Box sx={{ minWidth: 120 }}>
<form onSubmit={handleSubmit}>
<TextField
label="Product Name"
name="name"
type="text"
id="productName"
value={productName}
onChange={(e) => setProductName(e.target.value)}
required
/>
{sizeList.map((singleSize, index) => (
<div key={index}>
<TextField
label="Size"
name="size"
type="text"
id="size"
required
value={singleSize.size}
onChange={(e) => handleServiceChange(e, index)}
/>
{colorList.map((singleColor, index) => (
<div key={index}>
<TextField
label="color"
name="color"
type="text"
id="color"
required
value={singleColor.color}
onChange={(e) => handleColorStocksChange(e, index)}
/>
<TextField
label="Stocks"
name="colorStocks"
type="text"
id="colorStocks"
required
value={singleColor.colorStocks}
onChange={(e) => handleColorChange(e, index)}
/>
{colorList.length !== 1 && (
<Button onClick={() => handleColorRemove(index)}>
Remove
</Button>
)}
<br />
{colorList.length - 1 === index && (
<Button onClick={handleColorAdd}>Add Color</Button>
)}
<br /> <br />
{/* add or remove sizes */}
</div>
))}
{sizeList.length - 1 === index && (
<Button type="button" onClick={handleServiceAdd}>
Add size
</Button>
)}
{sizeList.length - 1 === index && (
<Button type="button" onClick={() => handleServiceRemove(index)}>
Remove Size
</Button>
)}
</div>
))}
<br />
<Button type="submit">Submit </Button>
</form>
<div className="output">
<h2>Output</h2>
<h3>Sizes:</h3>
{sizeList &&
sizeList.map((singleSize, index) => (
<ul key={index}>{singleSize.size && <li>{singleSize.size}</li>}</ul>
))}
<br />
<h3>Color:</h3>
{colorList &&
colorList.map((singleSize, index) => (
<ul key={index}>
{singleSize.color && (
<li>{singleSize.color + " - " + singleSize.colorStocks}</li>
)}
</ul>
))}
</div>
</Box>
);
}
Also in: https://www.reddit.com/r/reactjs/comments/sl41p5/handling_dynamic_textfields_values_are_duplicated/
Since you need an array of inputs, you need to maintain the states in that order.
See my updated sandbox code https://codesandbox.io/s/form-2-add-more-size-forked-yvrgg?file=/demo.js
import React, { useState, useEffect } from "react";
import Box from "#mui/material/Box";
import { TextField, Button } from "#mui/material";
export default function BasicSelect() {
const [productName, setProductName] = useState();
const [sizeList, setSizeList] = useState([{ size: "", colorList: [{ color: "", colorStocks: "" }] }]);
const [colorList, setColorList] = useState([{ color: "", colorStocks: "" }]);
//sizes
const handleServiceChange = (e, index) => {
const { name, value } = e.target;
const list = [...sizeList];
list[index] = { ...list[index] }; // copy the item too
list[index][name] = value;
setSizeList(list);
};
const handleServiceRemove = (index) => {
const list = [...sizeList];
list.splice(index, 1);
setSizeList(list);
};
const handleServiceAdd = () => {
setSizeList([...sizeList, { service: "", colorList: [{ color: "", colorStocks: "" }] }]);
};
// color
const handleColorChange = (e, index, innerIndex) => {
const { name, value } = e.target;
const list = [...sizeList];
list[index].colorList[innerIndex][name] = value;
setSizeList(list)
};
const handleColorRemove = (index) => {
const list = [...colorList];
list.splice(index, 1);
setColorList(list);
};
const handleColorAdd = (index) => {
const list = [...sizeList];
list[index].colorList.push({ color: "", colorStocks: "" });
setSizeList(list)
};
const handleSubmit = async (e) => {
e.preventDefault();
console.log("Product: ", productName, "size: ", sizeList, colorList);
};
return (
<Box sx={{ minWidth: 120 }}>
<form onSubmit={handleSubmit}>
<TextField
sx={{mb:2}}
label="Product Name"
name="name"
type="text"
id="productName"
value={productName}
onChange={(e) => setProductName(e.target.value)}
required
/>
{sizeList.map((singleSize, index) => (
<div key={index}>
<TextField
label="Size"
type="text"
name={`size${index}`}
id={`size${index}`}
required
value={singleSize.size}
onChange={(e) => handleServiceChange(e, index)}
/>
{singleSize.colorList.map((singleColor, innerIndex) => (
<div key={index}>
<TextField
label="color"
name="color"
type="text"
id="color"
required
value={singleColor.color}
onChange={(e) => handleColorChange(e, index, innerIndex)}
/>
<TextField
label="Stocks"
name="colorStocks"
type="text"
id="colorStocks"
required
value={singleColor.colorStocks}
onChange={(e) => handleColorChange(e, index, innerIndex)}
/>
{colorList.length !== 1 && (
<Button onClick={() => handleColorRemove(index)}>
Remove
</Button>
)}
<br />
{colorList.length - 1 === index && (
<Button onClick={()=>handleColorAdd(index)}>Add Color</Button>
)}
<br /> <br />
{/* add or remove sizes */}
</div>
))}
{sizeList.length - 1 === index && (
<Button type="button" onClick={handleServiceAdd}>
Add size
</Button>
)}
{sizeList.length - 1 === index && (
<Button type="button" onClick={() => handleServiceRemove(index)}>
Remove Size
</Button>
)}
</div>
))}
<br />
<Button type="submit">Submit </Button>
</form>
<div className="output">
<h2>Output</h2>
<h3>Sizes:</h3>
{sizeList &&
sizeList.map((singleSize, index) => (
<ul key={index}>{singleSize.size && <li>{singleSize.size}</li>}</ul>
))}
<br />
<h3>Color:</h3>
{colorList &&
colorList.map((singleSize, index) => (
<ul key={index}>
{singleSize.color && (
<li>{singleSize.color + " - " + singleSize.colorStocks}</li>
)}
</ul>
))}
</div>
</Box>
);
}
If you do something like below you wont have inputs with the same name
name=`size${index}`
id=`id${index}`
Here is my code
const useStyles = makeStyles((theme) => ({
root: {
'& .MuiTextField-root': {
margin: theme.spacing(1)
},
},
button: {
margin: theme.spacing(1)
}
}))
function CreateCourse() {
const classes = useStyles();
const [sectionFields, setSectionFields] = useState([{
sectionName: '',
overview: '',
videoContents: [{
videoName: '', videoUrl: ''
}]
}])
function handleChangInput(index, event) {
const values = [...sectionFields];
values[index][event.target.name] = event.target.value;
setSectionFields(values);
}
function handleChangVideoInput(index, i, event) {
const values = [...sectionFields];
values[index].videoContents[i][event.target.name] = event.target.value;
setSectionFields(values);
console.log(index, event.target.name)
}
const handleSubmit = (event) => {
event.preventDefault();
console.log("Input Field ", sectionFields)
}
const handleRemoveFields = (index) => {
const values = [...sectionFields];
values.splice(index, 1)
setSectionFields(values)
}
const handleAddFields = () => {
setSectionFields([...sectionFields, {
sectionName: '',
overview: '',
videoContents: [{videoName: '', videoUrl: ''}]
}])
}
return (
<div className='container mb-5'>
<Container>
<h1>Add New Member</h1>
<form className={classes.root} onSubmit={handleSubmit}>
{sectionFields.map((inputField, index) => (
<div key={index}>
<TextField
name="sectionName"
label="Section Name"
variant="filled"
value={inputField?.sectionName}
onChange={event => handleChangInput(index, event)}
/>
<TextField
name="overview"
label="Section Overview"
variant="filled"
value={inputField?.overview}
onChange={event => handleChangInput(index, event)}
/>
<IconButton onClick={() => handleRemoveFields(index)}>
<RemoveIcon/>
</IconButton>
<IconButton onClick={handleAddFields}>
<AddIcon/>
</IconButton>
{inputField?.videoContents?.map((v, i) => (
<div key={i}>
<TextField
name="videoName"
label="Enter Video Name"
variant="filled"
value={v.videoName}
onChange={event => handleChangVideoInput(index, i, event)}
/>
<TextField
name="videoUrl"
label="Enter Video Url"
variant="filled"
value={v.videoUrl}
onChange={event => handleChangVideoInput(index, i, event)}
/>
</div>
))}
</div>
))}
<Button
className={classes.button}
variant='contained'
color='primary'
type='submit'
endIcon={<Icon/>}
onClick={handleSubmit}
>
SEND
</Button>
</form>
</Container>
</div>
);
}
export default CreateCourse;
Output in Screenshot
when i click on plus icon creates a new input like
But I want one sectionName has many videoName and videoUrl like I want to create plus icon on the videoUrl side and when user clicks plus icon, it creates many videoName and videoUrl as many as user wants and if user clicks section then it creates one section row with one video row. How can I solve this using react?
First of all, when you use the current value of a state in order to calculate the new state value, it's preferable to use a callback function. This way it's not influenced by re-renders and guarantees the calculation uses the most updated state value.
So assuming you have
const [state, setState] = useState([]);
Don't use:
const next = [...state, newElement];
setState(next);
But instead, use:
setState((previous) => [...previous, newElement]);
In order to add more fields into a nested array, you can update the state like this:
function addToSection(i) {
setSectionFields((prev) => (
const updatedSection = {
...prev[i],
videoContents: [
...prev[i].videoContents,
{ videoName: '', videoUrl: '' },
],
};
return prev.map((section, index) => {
return index === i ? updatedSection : section;
});
);
}
After many hours of trying finally i did it and thanks to #GalAbra , he saves my lot of time and i post this because if it helps to anyone
import React, {useState} from "react";
import Container from "#material-ui/core/Container";
import {TextField} from "#material-ui/core";
import Icon from "#material-ui/icons/Send";
import {makeStyles} from "#material-ui/core/styles";
import Button from "#material-ui/core/Button";
import IconButton from "#material-ui/core/IconButton";
import RemoveIcon from '#material-ui/icons/Delete';
import AddIcon from "#material-ui/icons/Add";
const useStyles = makeStyles((theme) => ({
root: {
'& .MuiTextField-root': {
margin: theme.spacing(1)
},
},
button: {
margin: theme.spacing(1)
}}))
function CreateCourse() {
const classes = useStyles();
const [sectionFields, setSectionFields] = useState([{
sectionName: '',
overview: '',
videoContents: [{
videoName: '', videoUrl: ''
}]}])
function handleChangInput(index, event) {
const values = [...sectionFields];
values[index][event.target.name] = event.target.value;
setSectionFields(values);
}
function handleChangVideoInput(index, i, event) {
const values = [...sectionFields];
values[index].videoContents[i][event.target.name] = event.target.value;
setSectionFields(values);
console.log(index, event.target.name)
}
const handleSubmit = (event) => {
event.preventDefault();
console.log("Input Field ", sectionFields)
}
const handleRemoveFields = (index) => {
const values = [...sectionFields];
if(index > 0) values.splice(index, 1)
setSectionFields(values)
}
const handleRemoveVideoFields = (index, i) => {
const values = [...sectionFields];
if(i > 0)
values[index].videoContents.splice(i, 1)
setSectionFields(values)
}
const handleAddFields = (index) => {
setSectionFields((prevState => (
[...prevState, {
videoContents: [{videoName: '', videoUrl: ''}]
}]
)))
}
const handleAddVideoFields = (i) => {
setSectionFields(prev => {
const updatedSection = {
...prev[i],
videoContents: [
...prev[i].videoContents,
{videoName: '', videoUrl: ''},
],
};
return prev.map((section, index) => {
return index === i ? updatedSection : section;
});
})
}
return (
<div className='container mb-5'>
<Container>
<form className={classes.root} onSubmit={handleSubmit}>
{sectionFields.map((inputField, index) => (
<div key={index}>
<p className='mb-0 mt-3 ml-2'>Enter Section Name</p>
<TextField
name="sectionName"
label="Section Name"
variant="filled"
value={inputField?.sectionName}
onChange={event => handleChangInput(index, event)}
/>
<TextField
name="overview"
label="Section Overview"
variant="filled"
value={inputField?.overview}
onChange={event => handleChangInput(index, event)}
/>
<IconButton onClick={() => handleRemoveFields(index)}>
<RemoveIcon/>
</IconButton>
<IconButton onClick={() => handleAddFields(index)}>
<AddIcon/>
</IconButton>
{inputField?.videoContents?.map((v, i) => (
<div key={i}>
<TextField
name="videoName"
label="Enter Video Name"
variant="filled"
value={v.videoName}
onChange={event => handleChangVideoInput(index, i, event)}
/>
<TextField
name="videoUrl"
label="Enter Video Url"
variant="filled"
value={v.videoUrl}
onChange={event =>
handleChangVideoInput(index, i, event)}
/>
<IconButton onClick={() =>
handleRemoveVideoFields(index, i)}>
<RemoveIcon/>
</IconButton>
<IconButton onClick={() =>
handleAddVideoFields(index)}>
<AddIcon/>
</IconButton>
</div>
))}
</div>
))}
<Button
className={classes.button}
variant='contained'
color='primary'
type='submit'
endIcon={<Icon/>}
onClick={handleSubmit}
>
SEND
</Button>
</form>
</Container>
</div>
);
}
export default CreateCourse;
I am unable to get the values of email and password from the FormControl Input values. I am using the BaseWeb ReactUI framework for the field UI.
Need help stuck on this issue.
import { FormControl } from 'baseui/form-control';
import { Input } from 'baseui/input';
import { useStyletron } from 'baseui';
import { Alert } from 'baseui/icon';
import { Button } from 'baseui/button';
import { TiUserAddOutline } from "react-icons/ti";
import Axios from "axios";
import { useHistory, Link } from 'react-router-dom';
import { validate as validateEmail } from 'email-validator'; // add this package to your repo with `$ yarn add email-validator`
import { Select } from 'baseui/select';
import { Checkbox } from 'baseui/checkbox';
function SelectAtStart(props) {
const [css] = useStyletron();
return (
<div className={css({ display: 'flex' })}>
<div className={css({ width: '200px', paddingRight: '8px' })}>
<Select
options={props.options}
labelKey="id"
valueKey="gender"
onChange={({ value }) => props.onSelectChange(value)}
value={props.selectValue}
id={props.id}
/>
</div>
<Input
onChange={e => props.onInputChange(e.target.value)}
value={props.inputValue}
/>
</div>
);
}
function Negative() {
const [css, theme] = useStyletron();
return (
<div
className={css({
display: 'flex',
alignItems: 'center',
paddingRight: theme.sizing.scale500,
color: theme.colors.negative400,
})}
>
<Alert size="18px" />
</div>
);
}
export function RegisterFields() {
const history = useHistory();
const [css, theme] = useStyletron();
const [email, setEmail] = React.useState('');
const [password, setPassword] = React.useState('');
const [checked, setChecked] = React.useState(true);
const [startInputValue, setStartInputValue] = React.useState('');
const [startSelectValue, setStartSelectValue] = React.useState(
[],
);
const [isValid, setIsValid] = React.useState(false);
const [isVisited, setIsVisited] = React.useState(false);
const shouldShowError = !isValid && isVisited;
const onChangeEmail = ({ target: { email } }) => {
setIsValid(validateEmail(email));
setEmail(email);
};
const onChangePassword = ({ target: { password } }) => {
setIsValid(true);
setPassword(password);
}
const handleSubmit = (event) => {
event.preventDefault();
console.log(email, password)
Axios.defaults.headers.common = {
"Content-Type": "application/json"
}
Axios.post("http://localhost:5000/api/signup", {
email: email,
password: password,
firstName: startInputValue,
lastname: startInputValue,
agreement: checked
}).then((res) => {
if (res.status === 200) {
const path = '/dashboard'
history.push(path)
console.log(res)
}
else {
console.log("Unable to create account")
}
})
}
return (
<form
onSubmit={handleSubmit}
className={css({
marginTop: theme.sizing.scale1000,
})}
>
<FormControl
label="Your Email"
error={
shouldShowError
? 'Please input a valid email address'
: null
}
>
<Input
id="email"
value={email}
onChange={onChangeEmail}
onBlur={() => setIsVisited(true)}
error={shouldShowError}
overrides={shouldShowError ? { After: Negative } : {}}
type="email"
required
/>
</FormControl>
<FormControl
label="Your Password"
error={
shouldShowError
? 'Your password is incorrect'
: null
}
>
<Input
id="password"
value={password}
onChange={onChangePassword}
onBlur={() => setIsVisited(true)}
error={shouldShowError}
overrides={shouldShowError ? { After: Negative } : {}}
type="password"
required
/>
</FormControl>
<FormControl
label="Your Full Name"
>
<SelectAtStart
inputValue={startInputValue}
onInputChange={v => setStartInputValue(v)}
selectValue={startSelectValue}
onSelectChange={v => setStartSelectValue(v)}
options={[
{ id: 'Mr', gender: 'Male' },
{ id: 'Mrs', gender: 'Women' },
{ id: 'Ms', gender: 'Female' },
{ id: 'None', gender: 'Dont Say' },
]}
id="start-id"
/>
</FormControl>
<FormControl>
<Checkbox
checked={checked}
onChange={() => setChecked(!checked)}
>
<div className={css({
color:'grey'
})}
>I hereby agree to the terms and conditons of the platform.</div>
</Checkbox>
</FormControl>
<Button type="submit">
<TiUserAddOutline style={{ marginRight: '10px' }} />
Create Account
</Button>
<div>
<p style={{ marginTop: '10px', color: 'grey' }} >To go back to login, please <Link to='/'>click here</Link></p>
</div>
</form>
);
}
I believe it's because you keep setting it to the email/password value of target
which is "undefined".
e.target.email = undefined
e.target.password = undefined
Try with this approach:
const onChangeEmail = e =>
{
setIsValid(validateEmail(e.target.value));
setEmail(e.target.value);
};
const onChangePassword = e =>
{
setIsValid(true);
setEmail(e.target.value);
};
Here you can see that all text inputs are stored in a value of targeted event ( event.target.value )
I'm doing application with friends. I'm using react-hook-form. The api gets only null values. It's a very crucial element of our application. Please help, here is my code:
const NewItemBar = ({ isVisible, handleClose }) => {
const { register, handleSubmit, watch, errors } = useForm();
const onSubmit = ({ name, data, description }) => {
api
.post(endpoints.createTask, {
name,
data,
description,
})
.then((response) => {
console.log(response);
})
.catch((err) => console.log(err));
};
return (
<StyledWrapper handleClose={handleClose} isVisible={isVisible}>
<StyledHeading big>Dodaj zadanie do wykonania</StyledHeading>
<StyledForm onSubmit={handleSubmit(onSubmit)}>
<StyledInput
placeholder="nazwa zadania"
type="text"
name="name"
{...register('name', { required: 'Required' })}
/>
<StyledInput
placeholder="data wykonania"
type="text"
name="data"
{...register('data', { required: 'Required' })}
/>
<StyledTextArea
type="text"
placeholder="opis"
name="description"
as="textarea"
{...register('description', { required: 'Required' })}
/>
<StyledButton type="submit">Zapisz</StyledButton>
</StyledForm>
</StyledWrapper>
);
};
In case of custom components you should create wrapper using Controller or pass a function with forwardRef.
https://react-hook-form.com/get-started#IntegratingwithUIlibraries
forwardRef might be useful when you want your component to manage inner state on it's own.
For example:
const PhoneInput = forwardRef(
(
{
id,
name,
label,
placeholder,
errorMsg,
onSubmit,
onChange,
onBlur,
disabled,
},
ref
) => {
const [value, setValue] = useState("");
const _onChange = (value) => {
setValue(value);
onChange(value);
};
const classes = (className) =>
[
className,
disabled ? "disabled" : "",
errorMsg ? "is-invalid" : "",
].join(" ");
console.log(value);
return (
<div className={classes("f-form-group phone-input")}>
<div className="phone-input-wrap">
<div className="inputs">
<label htmlFor={name}>{label}</label>
<NumberFormat
className={classes("f-form-control")}
name={name}
id={id}
type="tel"
format="+7 (###) ### ##-##"
mask="_"
placeholder={placeholder}
value={value}
onChange={(e) => _onChange(e.target.value)}
onBlur={onBlur}
getInputRef={ref}
/>
</div>
<button
type="button"
className="btn btn-primary"
onClick={() => onSubmit(value)}
>
Подтвердить
</button>
</div>
<div className="invalid-feedback">{errorMsg}</div>
</div>
);
}
);
I have a react form that calls a graphql mutation when the button is clicked. After the button is clicked and the mutation is completed, the text in the form is still there. So in order to run a new mutation, the user will have to manually remove the text written in the text fields.
Is there any way to automatically reset the text fields once the form is submitted?
export default function RemoveUserPage() {
const [isSubmitted, setIsSubmitted] = useState(false);
const [isRemoved, setIsRemoved] = useState(false);
const [errorMessage, setErrorMessage] = useState('');
const [removeUser] = useMutation<DeleteUserReponse>(REMOVE_USER);
let submitForm = (email: string) => {
setIsSubmitted(true);
removeUser({
variables: {
email: email,
},
})
.then(({ data }: ExecutionResult<DeleteUserReponse>) => {
if (data !== null && data !== undefined){
setIsRemoved(true);
}})
.catch((error: { message: string }) => {
setIsRemoved(false);
setErrorMessage(error.message);
});
};
const initialValues={ email: '' }
return (
<div>
<PermanentDrawerLeft></PermanentDrawerLeft>
<Formik
//initialValues={{ email: '' }}
initialValues={initialValues}
onSubmit={(values, actions) => {
setTimeout(() => {
alert(JSON.stringify(values, null, 2));
actions.setSubmitting(false);
}, 1000);
initialValues={initialValues}
}}
validationSchema={schema}>
{props => {
const {
values: { email },
errors,
touched,
handleChange,
isValid,
setFieldTouched,
} = props;
const change = (name: string, e: FormEvent) => {
e.persist();
handleChange(e);
setFieldTouched(name, true, false);
};
return (
<div>
<form
style={{ width: '100%' }}
onSubmit={e => {
e.preventDefault();
submitForm(email);
}}>
<div>
<TextField
variant="outlined"
margin="normal"
id="email"
name="email"
helperText={touched.email ? errors.email : ''}
error={touched.email && Boolean(errors.email)}
label="Email"
value={email}
onChange={change.bind(null, 'email')}
/>
<CustomButton
disabled={!isValid || !email}
text={'Remove User'}
/>
</div>
</form>
{isSubmitted && StatusMessage(isRemoved, errorMessage)}
</div>
);
}}
</Formik>
</div>
);
}
Try reusing your initial state, something like this:
const INITIAL_STATE = { email: '' };
export default function RemoveUserPage() {
const [isSubmitted, setIsSubmitted] = useState(false);
const [isRemoved, setIsRemoved] = useState(false);
const [errorMessage, setErrorMessage] = useState('');
const [removeUser] = useMutation<DeleteUserReponse>(REMOVE_USER);
let submitForm = (email: string) => {
setIsSubmitted(true);
removeUser({
variables: {
email: email,
},
})
.then(({ data }: ExecutionResult<DeleteUserReponse>) => {
if (data !== null && data !== undefined){
setIsRemoved(true);
}})
.catch((error: { message: string }) => {
setIsRemoved(false);
setErrorMessage(error.message);
});
};
return (
<div>
<PermanentDrawerLeft></PermanentDrawerLeft>
<Formik
initialValues={{ ...INITIAL_STATE }}
onSubmit={(values, actions) => {
setTimeout(() => {
alert(JSON.stringify(values, null, 2));
actions.setSubmitting(false);
}, 1000);
actions.setValues({ ...INITIAL_STATE });
}}
validationSchema={schema}>
{props => {
const {
values: { email },
errors,
touched,
handleChange,
isValid,
setFieldTouched,
} = props;
const change = (name: string, e: FormEvent) => {
e.persist();
handleChange(e);
setFieldTouched(name, true, false);
};
return (
<div>
<form
style={{ width: '100%' }}
onSubmit={e => {
e.preventDefault();
submitForm(email);
}}>
<div>
<TextField
variant="outlined"
margin="normal"
id="email"
name="email"
helperText={touched.email ? errors.email : ''}
error={touched.email && Boolean(errors.email)}
label="Email"
value={email}
onChange={change.bind(null, 'email')}
/>
<CustomButton
disabled={!isValid || !email}
text={'Remove User'}
/>
</div>
</form>
{isSubmitted && StatusMessage(isRemoved, errorMessage)}
</div>
);
}}
</Formik>
</div>
);
}
Although I've also seen resetForm method on second parameter of onSubmit, but haven't tested it how it works, so you can try that also.