How to make field validation?
I have an object with fields from which I generate a form, and when submitting, I need to check each field so that it is not empty, I do this, but it doesn’t work
My form:
const [volunteerData, setVolunteerData] = useState({
fullName: {
value: '',
type: "text",
placeholder: "Name",
label: "Name"
},
phone: {
value: '',
type: "text",
placeholder: "Phone number",
label: "Phone number",
mask: "+7(999) 999 99 99"
}
)}
Render form:
const onHandleRenderForm = () => {
return Object.keys(volunteerData).map((items, idx) => {
const control = volunteerData[items];
return (
<div key={idx} className="content-data-box">
<label>{control.label}</label>
<InputMask
type={control.type}
placeholder={control.placeholder}
mask={control.mask}
onChange={e => onHandleFormData(e, items)}
/>
</div>
)
})
};
onChange input:
const onHandleFormData = (e, items) => {
const before = {...volunteerData};
const after = {...before[items]}
after.value = e.target.value;
before[items] = after;
setVolunteerData(before);
};
onClick (submit button):
const onHandleErrorBoundary = () => {
Object.keys(volunteerData).map(items => {
const errorData = items.value === '';
console.log(errorData)
})
};
Change items.value === '' to volunteerData[items].value !== ""
const onHandleErrorBoundary = () => {
Object.keys(volunteerData).map(items => {
const errorData = volunteerData[items].value !== "";
return console.log(errorData);
});
};
you can check here codesandbox
Related
I have a form. Initially there is some default values (user name and address). When user click add, there is an extra input which user can enter another name and address, and the extra name and address will store in additionConfigs.
Example:
https://codesandbox.io/s/elastic-pateu-2uy4rt
import "./styles.css";
import { useState, useEffect } from "react";
export default function App() {
const [value, setValue] = useState([]);
const [additionConfigs, setAdditionConfigs] = useState([]);
useEffect(() => {
setTimeout(() => {
setValue([
{
id: 1,
baseName: "XXX",
config: {
name: "Kenny",
address: "New york"
}
},
{
id: 2,
baseName: "YYY",
config: {
name: "Ben",
address: "Boston"
}
},
{
id: 3,
baseName: "ZZZ",
config: {
name: "Mary",
address: "Los Angeles"
}
}
]);
}, 1000);
}, []);
const onAddBaseConfig = (item) => {
setAdditionConfigs((preValue) => [
...preValue,
{
id: item.id,
config: {
name: "",
address: ""
}
}
]);
};
console.log(additionConfigs);
const onChangeName = (e, id) => {
setAdditionConfigs((preValue) => {
const newValue = preValue.map((v) => {
if (v.id === id) {
return {
...v,
config: {
...v.config,
name: e.target.value
}
};
}
return v;
});
return newValue;
});
};
const onChangeAddress = (e, id) => {
setAdditionConfigs((preValue) => {
const newValue = preValue.map((v) => {
if (v.id === id) {
return {
...v,
config: {
...v.config,
address: e.target.value
}
};
}
return v;
});
return newValue;
});
};
return (
<>
{value.length > 0 &&
value.map((v, index) => (
<div className="item" key={index}>
<div className="item">
{v.config.name}
{v.config.address}
{additionConfigs.length > 0 &&
additionConfigs
.filter((config) => config.id === v.id)
.map((config) => (
<div>
<label>name</label>
<input
value={config.config.name}
onChange={(e) => onChangeName(e, config.id)}
/>
<label>address</label>
<input
value={config.config.address}
onChange={(e) => onChangeAddress(e, config.id)}
/>
</div>
))}
</div>
<button onClick={() => onAddBaseConfig(v)}>Add</button>
</div>
))}
</>
);
}
Currently, I use config.id to update the extra name and address, but there is an issue that if user add two or more extra name and address input, when updating the first one, the second will update, too.
How do I update respectively? Giving each group of input a flag?
Assuming that the component should not modify the base value as it is set by a useEffect, but keep a additionConfigs which need to support any amount of config inputs, perhaps one solution could be to make additionConfigs state an object.
The additionConfigs object could have id from base value as key and an array of configs as value, and perhaps each config need its own id, so that they can be controlled by the added input, without major refactor of the existing code structure.
Forked live with modifications: codesandbox
Perhaps try the following as an example:
Define additionConfigs state as an object:
const [additionConfigs, setAdditionConfigs] = useState({});
Update logic for additionConfigs when adding a config input:
(The id logic here is only adding previous id++, and should probably be replaced by a unique id generator in actual project)
const onAddBaseConfig = (item) => {
setAdditionConfigs((preValue) => {
const preConfigs = preValue?.[item.id];
const newId = preConfigs
? preConfigs.reduce((acc, cur) => (cur.id > acc ? cur.id : acc), 0) + 1
: 1;
return {
...preValue,
[item.id]: preConfigs
? [
...preConfigs,
{
id: newId,
config: {
name: "",
address: ""
}
}
]
: [
{
id: newId,
config: {
name: "",
address: ""
}
}
]
};
});
};
Update logic for a config input for name, a baseId is added as an argument as each base value can have multiple configs:
const onChangeName = (e, id, baseId) => {
setAdditionConfigs((preValue) => {
const newArr = preValue[baseId].map((v) => {
if (v.id === id) {
return {
...v,
config: {
...v.config,
name: e.target.value
}
};
}
return v;
});
return { ...preValue, [baseId]: newArr };
});
};
Same but for address:
const onChangeAddress = (e, id, baseId) => {
setAdditionConfigs((preValue) => {
const newArr = preValue[baseId].map((v) => {
if (v.id === id) {
return {
...v,
config: {
...v.config,
address: e.target.value
}
};
}
return v;
});
return { ...preValue, [baseId]: newArr };
});
};
Output with the changes:
<>
{value.length > 0 &&
value.map((v, index) => (
<div className="item" key={index}>
<div className="item">
{v.config.name}
{v.config.address}
{additionConfigs?.[v.id] &&
additionConfigs?.[v.id].length > 0 &&
additionConfigs?.[v.id].map((config, index) => (
<div key={config.id}>
<label>name</label>
<input
value={config.config.name}
onChange={(e) => onChangeName(e, config.id, v.id)}
/>
<label>address</label>
<input
value={config.config.address}
onChange={(e) => onChangeAddress(e, config.id, v.id)}
/>
</div>
))}
</div>
<button onClick={() => onAddBaseConfig(v)}>Add</button>
</div>
))}
</>
Quick explanation of problem in this image. It's kind of product list where last row is empty and on filling quantity column a new row should appear. Problem is when you have added few new rows and you go back try changing previous rows the newly created rows disappears beyond that point where you made the change.
SANDBOX with all code replicating issue > https://codesandbox.io/s/react-hooks-playground-forked-xbkrub
Index (Main entry point)
import * as React from "react";
import { useState, useEffect } from "react";
import { render } from "react-dom";
import Input from "./Input";
function App() {
const [products, setProducts]: any = useState([]);
const getProductsAsync = () =>
new Promise((resolve, reject) => {
setTimeout(() => {
const prodsArray = [
{
id: 371337,
barcode: null,
name: "4x Cheese",
details: null,
tags: null,
unit: null,
productTimestamp: "2022-01-26T11:40:34.000Z",
activeDate: null,
deleteDate: null,
price: 1
},
{
id: 372093,
barcode: "",
name: "Capri Sun",
details: "",
tags: "",
unit: null,
productTimestamp: "2022-05-11T13:43:37.000Z",
activeDate: "2022-05-11T13:43:37.000Z",
deleteDate: null,
categories: { "924": "Juices", "13524": "3 for £11" },
price: 1
},
{
id: 372552,
barcode: "",
name: "Hot Chocolate",
details: "",
tags: "",
unit: null,
productTimestamp: "2022-05-25T11:15:40.000Z",
activeDate: "2022-05-25T11:15:40.000Z",
deleteDate: null,
categories: { "924": "Juices" },
price: 1
},
{
id: 372553,
barcode: "",
name: "Orange",
details: "",
tags: "",
unit: null,
productTimestamp: "2022-05-25T11:16:06.000Z",
activeDate: "2022-05-25T11:16:06.000Z",
deleteDate: null,
categories: { "924": "Juices" },
price: 1
},
{
id: 372598,
barcode: "",
name: "PRODUCT",
details: "",
tags: "",
unit: null,
productTimestamp: "2022-06-06T14:22:00.000Z",
activeDate: "2022-06-06T14:22:00.000Z",
deleteDate: null,
price: 1
}
];
resolve(prodsArray);
}, 1000);
});
useEffect(() => {
(async () => {
const productObjects: any = await getProductsAsync();
//add a new row at the end for adding more data.
addNewEmptyRow(productObjects);
})();
}, []);
useEffect(() => {}, [products]);
const onPriceChange = (index, event) => {
if (event !== undefined && event.target !== undefined) {
//make sure you supply name="fieldName you want to target in your html tag i.e <input name="taxPercentage">"
const fieldName = event.target.getAttribute("name");
const inputValue = event.target.value;
if (inputValue !== undefined && inputValue !== null) {
const prodComps = products.map((item, currIndex) => {
if (currIndex === index) {
let row = { ...item };
row[fieldName] = inputValue;
return row;
} else {
return item;
}
});
setProducts([...prodComps]);
addNewEmptyRow([...prodComps]);
}
}
};
const addNewEmptyRow = (list: any, rowToAdd?: any) => {
const lastRow = rowToAdd || list[list.length - 1];
if (
lastRow.price !== undefined &&
lastRow.price !== null &&
lastRow.price.toString().length > 0
) {
// enableCrossButtonForLastRow(list);
const rows: any = [
...list,
{
price: "",
vatPercentage: "",
tags: "",
name: " ",
id: null,
autoGenIdForNewlyAddedRow: Date.now(),
showCross: false
}
];
setProducts(rows);
}
};
const generateList: any = () => {
return products.map((item: any, index) => {
return (
<React.Fragment key={item.id || item.autoGenIdForNewlyAddedRow}>
<div>
<input type="text" name="name" value={item.name} readOnly={true} />
</div>
<div>
<Input
type="text"
name="price"
value={item.price}
onChange={(e) => onPriceChange(index, e)}
/>
</div>
<div>X</div>
</React.Fragment>
);
});
};
return (
<div>
<div
style={{
display: "grid",
gridTemplateColumns: "[nameOverride] 175px [price] 155px [cross] 65px"
}}
>
{generateList()}
</div>
</div>
);
}
render(<App />, document.getElementById("root"));
Input.tsx (Custom input component)
import * as React from "react";
import { useEffect, useRef, useState } from "react";
const Input = (props) => {
const [value, setValue] = useState("");
const inputRef: any = useRef(null);
const valueRef = useRef("");
useEffect(() => {
inputRef.current.addEventListener("input", handleChange);
}, []);
useEffect(() => {
if (Boolean(props.value)) {
//this logic sets the data to 2 decimal places
if (props.decimalplaces) {
const val = parseFloat(props.value).toFixed(props.decimalplaces);
valueRef.current = val;
setValue(val);
} else {
valueRef.current = props.value;
setValue(props.value);
}
}
}, [props]);
const handleChange = (e) => {
const val = e.target.value;
if (props.decimalplaces && val.match(/\./g)) {
const [, decimal] = val.split(".");
// restrict value to only 2 decimal places
if (decimal?.length > props.decimalplaces) {
inputRef.current.value = valueRef.current;
} else {
valueRef.current = e.target.value;
}
} else {
valueRef.current = val;
}
if (Boolean(props.onChange)) {
//this builds the custom event object similar to the javascript event object.
props.onChange(
buildEventObject({
value: valueRef.current,
name: props.name,
type: props.type
})
);
}
};
const buildEventObject = (params) => {
return {
target: {
value: params.value,
getAttribute: (key) => {
return params[key];
}
}
};
};
return (
<div>
<input type={props.type} ref={inputRef} defaultValue={value} />
</div>
);
};
export default Input;
I have a form.
This returns back and loads its value which is correct,
however when I start typing it only returns its value plus one letter from the onChange.
here is what I mean
How do I clear the value of the form when the onChange is executed? I have tried clearing the form using config.setStateVar('') which still does not seem to work. Does anyone have any ideas?
const onChange = (e, config) => {
config.setStateVar('')
const value = e.target.innertext ? e.target.innertext
: e.target.value
config.setStateVar(value)
}
Full Code:
const editCriticalObjects = props => {
const query = new URLSearchParams(props.location.search)
const criticalObjectsId = query.get('id')
const loading = useContext(LoadingContext)
const user = useContext(UserContext)
const [editCriticalObjects, setEditCriticalObjects] = useState([])
const [criticalObjectTitle, setCriticalObjectTitle] = useState('')
const [criticalObjectDomainName, setCriticalObjectDomainName] = useState('')
const [criticalObjectType, setCriticalObjectType] = useState('')
const [findVal, setFindVal] = useState('')
useEffect(() => {
async function onLoadCriticalObjects() {
loading.setLoading(true)
const results = await get(`get_critical_objects?id=${criticalObjectsId}`, user.user)
setEditCriticalObjects(results)
loading.setLoading(false)
}
onLoadCriticalObjects()
}, [])
const assetIdObject = editCriticalObjects.filter(obj => {
if (obj.asset_id === criticalObjectsId) {
return obj
}
return assetIdObject
})
const configs = [
{
label: 'Title',
name: 'Title',
value: assetIdObject.map(item => item.asset_id),
field: SinglelineTextfield,
uniqueIdentifier: 0,
stateVar: criticalObjectTitle,
setStateVar: setCriticalObjectTitle,
},
{
name: 'type',
uniqueIdentifier: 1,
label: 'type',
value: assetIdObject.map(item => item.type),
field: SinglelineTextfield,
stateVar: criticalObjectType,
setStateVar: setCriticalObjectType,
},
{
name: 'Domain',
label: 'Domain',
value: assetIdObject.map(item => item.Domains),
field: SinglelineTextfield,
uniqueIdentifier: 2,
stateVar: criticalObjectDomainName,
setStateVar: setCriticalObjectDomainName,
}
]
const criticalObjectParams = {
asset_ids: criticalObjectTitle,
asset: true,
merge: true,
}
console.log(criticalObjectParams)
const onChange = (e, config) => {
const value = e.target.innertext ? e.target.innertext
: e.target.value
config.setStateVar(value)
}
const onSubmitClick = async () => {
loading.setLoading(true)
await verifiedPost('post_critical_objects', criticalObjectParams, user.user)
loading.setLoading(false)
}
return (!editCriticalObjects ? null :
<PageWrapper title={`Edit Critical Object: ${criticalObjectsId}`}>
<div className='Main Card EditCriticalObjectFormWrapper'>
{configs.map((config, k)=> {
const Field = config.field
return (
<React.Fragment key={k}>
<Field
style={{ marginBottom: '15px'}}
name={config.name}
placeholder={config.name}
value={config.value}
initialValue={config.value}
initialValues={config.value}
uniqueIdentifier={k}
label={config.label}
onChange={(e) => onChange(e, config)}
/>
</React.Fragment>
)
})}
<Button
className='Button Dark Main'
text='Submit'
onClick={onSubmitClick}
/>
</div>
</PageWrapper>
)
}
export default editCriticalObjects
I have two selects and I want to populate the second select base in the selection of the first one in react. When I select a countrie I want a select2 be displayed with its states and the value on the second select be updated with the value chose.
I have the following code,
const MyForm = (props) => {
const COUNTRIES = [
{
displayValue: "Country1",
value: "C1"
},
{
displayValue: "Country2",
value: "C2"
}
]
const STATES = {
"": [ {
displayValue: "",
value: ""
}],
"C1": [{
displayValue: "State 1",
value: "S11"
},
{
displayValue: "State 2",
value: "S12"
}],
"C2": [{
displayValue: "State n1",
value: "C21"
},
{
displayValue: "STate n2",
value: "C22"
}]
}
let inputsForms = {
country: {
elementType: 'select',
elementConfig: {
type: 'select',
placeholder: '',
options: COUNTRIES,
firstOption: "-- Choose Country"
},
value: ''
},
states: {
elementType: 'select',
elementConfig: {
type: 'select',
placeholder: '',
options: [], // I need these options depend on the countrie selected STATES["C1"]
firstOption: "-- Choose States"
},
value: ''
}
}
const [myForm, setmyForm] = useState(inputsForms);
const updateObject = (oldObject, updatedProperties) => {
return {
...oldObject,
...updatedProperties
};
};
const inputChangedHandler = (e, controlName) => {
const countrieValue = controlName ==="country"?e.target.value:"";
const stateOptions = myForm["states"].elementConfig;
stateOptions["options"] = STATES[countrieValue];
const updatedControls = updateObject(myForm, {
[controlName]: updateObject(myForm[controlName], {
value: e.target.value
})
});
setmyForm(updatedControls);
}
const ElementsArray = [];
for (let key in myForm) {
ElementsArray.push({
id: key,
config: myForm[key]
});
}
let form = (
<form>
{ElementsArray.map(el => (
<Input
key={el.id}
elementType={el.config.elementType}
elementConfig={el.config.elementConfig}
value={el.config.value}
changed={e => inputChangedHandler(e, el.id)}
firstOption={el.config.firstOption}
/>
))}
</form>
);
return(
<div>
{form}
</div>
);
}
export default MyForm;
The options charge on the select2, however when I select an option on the second select, the options dissappear and the value of the select2 is not updated.
Thanks.
As inputChangeHandler is getting called every input change, the data is resetting even there is change in the state. You could check for the contrieValue and set the state data so the data is not reset.
const inputChangedHandler = (e, controlName) => {
const countrieValue = controlName === "country" ? e.target.value : "";
if (countrieValue) {
const stateOptions = myForm["states"].elementConfig;
stateOptions["options"] = STATES[countrieValue];
const updatedControls = updateObject(myForm, {
[controlName]: updateObject(myForm[controlName], {
value: e.target.value
})
});
setmyForm(updatedControls);
}
}
You need to add a kind of conditional to update the state whenever the state is selected so that it does not affect the original country object.
const inputChangedHandler = (e, controlName) => {
if (countrolName === 'states') {
// your logic here
return;
}
const countrieValue = controlName === 'country' ? e.target.value : '';
const stateOptions = myForm['states'].elementConfig;
stateOptions['options'] = STATES[countrieValue];
const updatedControls = updateObject(myForm, {
[controlName]: updateObject(myForm[controlName], {
value: e.target.value,
}),
});
setmyForm(updatedControls);
};
I want to add form fields dynamically.
I also want that on click the durationprice add dynamically and also a button to add dynamically full plan.
{ plan: [ { planname: "", description: "", cuisine: "", durationprice: [{duration: "",maximumduration: "", price: ""}]}]}
import React, {
Component,
useState
} from "react";
import DurationPrice from "./DurationandPrice";
import Rules from "./Rules";
const TOKEN = 'hello';
export function PlanForm() {
const [inputList, setInputList] = useState([{
planName: "",
description: "",
cuisine: "",
}, ]);
const handleChnage = (e, index) => {
const {
name,
value
} = e.target;
const list = [...inputList];
list[index][name] = value;
setInputList(list);
};
const handleInput = () => {
const list = [...inputList];
list.push({
planName: "",
description: "",
cuisine: ""
});
setInputList(list);
// setInputList([...inputList,{firstName:'',lastName:''}]);
};
const handleRemove = (index) => {
const list = [...inputList];
list.splice(index, 1);
setInputList(list);
};
const handleSubmit = (e) => {
e.preventDefault();
};
const handleSave = (e) => {
e.preventDefault();
console.log('button clicked');
fetch(`https://cosynest-api.herokuapp.com/api/plans/create`, {
method: 'POST',
body: JSON.stringify({
'duration': inputList.duration,
'maximumDuration': inputList.maximumDuration,
'price': inputList.price
}),
headers: new Headers({
Authorization: `Bearer ${TOKEN}`,
'content-type': 'application/json'
})
})
.then(response => response.json())
.then(console.log(inputList))
.then(localStorage.setItem('Token-CreateShop', TOKEN))
.catch(console.log('error'))
}
// debugger;
return ( <
div className = "plan-form" > {
inputList.map((item, i) => {
return ( <
form key = {
i
}
onSubmit = {
handleSubmit
} >
<
input type = "text"
name = "planName"
className = "mr-10 input "
placeholder = "Plan Name"
value = {
item.planName
}
onChange = {
(e) => handleChnage(e, i)
}
/> <
input type = "text"
name = "description"
className = "mr-10 input "
placeholder = "Description"
value = {
item.description
}
onChange = {
(e) => handleChnage(e, i)
}
/> <
input type = "cuisine"
onChange = {
(e) => handleChnage(e, i)
}
value = {
item.cuisine
}
className = "mr-10 input "
name = "cuisine"
placeholder = "Cuisine" /
>
<
h2 > Duration and Price < /h2> <
DurationPrice / >
<
br / >
<
div > {
inputList.length - 1 === i && ( <
button type = "button"
onClick = {
handleInput
}
className = "mr-10 btn"
value = "Add Plan" >
Add Plan <
/button>
)
} {
inputList.length !== 1 && ( <
button onClick = {
() => handleRemove(i)
}
className = "mr-10 btn-red"
value = "Remove Plan" >
Remove <
/button>
)
} <
/div>
<
/form>
);
})
} <
button onClick = {
handleSave
}
className = "btn-pink btn "
type = "submit" > Save PLANS < /button> {
/* <pre>
{JSON.stringify(inputList,null,3)}
</pre> */
} <
/div>
);
}
export default PlanForm;
you can use one state that will have all the formData states inside it and for the input elements you can use map to render
lets say you have to create form for these fields
let formElements = [{
label: "Name", // this is label
key: 'name' // this is unique key to be used as state name
}, {
label: "Phone number",
key: 'phone'
}, {
label: "House",
key: 'house'
}, {
label: "City",
key: 'city'
}, {
label: "State",
key: 'state'
}, {
label: "Country",
key: 'country'
}]
now you can create your component from this element something like this
export default function App() {
const [formData, setFormData] = useState({} as any);
const handleChange = (value: string, key: string) => {
setFormData({ ...formData, ...{ [key]: value } });
}
const submit = () => {
alert(JSON.stringify(formData))
}
return (
<form>
Form goes here
{formElements.map(formElement => {
return <div>
{formElement.label}
<input value={formData[formElement.key]}
onChange={(e) => { handleChange(e.target.value, formElement.key) }} />
</div>
})}
<button onClick={(e) => { e.preventDefault(); submit() }}>submit</button>
</form>
);
}
Now whenever you want to update the form fields just update your formElements Array.
in your case you can store formElements in state and update that like this
const [formElement,setFormElements] = useState([]);
useEffect(()=>{
setFormElements(formElement)
},[]);
// update your state with new element like this on button click
const updateFormElememt = (newElement)=>{
setFormElements([...formElements,...newElement])
}