So in my application user have an option to enter a number, for example if user enters as "5" then it will add 5 textInputs. By this, i have successfully looped the textInput. Now, how to populate or store the values in the state ?
My code :
this.state =
{
usersDetails: [{name: "", age: "", gender: "", primary: false}, {name: "", age: "", gender: "", primary: false}]
};
handleChange(i, e) {
const { name, value } = e;
let users = [...this.state.usersDetails];
users[i] = {...users[i], [name]: value};
this.setState({ usersDetails });
console.log(this.state.usersDetails);
}
let items = [];
for (let i = 0; i < this.props.maxSeats; i++) {
items.push(
<TextInput
placeholder="Enter Name"
onChangeText={this.handleChange.bind(this, i)}
value={this.state.userDetails}
/>)
}
With the help of stockoverflow i created the handleChange function but it gives error ! How to populate the values and save on the state for dynamic form !
Kindly guide !
handleChange = (value, index,key) => {
this.setState((prevState) => ({
...prevState,
usersDetails: prevState.usersDetails.map((val, mapIndex) => {
if (mapIndex === index) {
val[key] = value;
return val;
}
return val;
}),
}));
};
for (let i = 0; i < this.props.maxSeats; i++) {
items.push(
<TextInput
key={i}
placeholder="Enter Name"
onChangeText={(text) => {
handleChange(text, i,'name'); // here you can pass name,age whatever
}}
value={this.state.userDetails[i].name}
/>,
);
}
Related
I am trying to create an application that has dynamic text field input using MUI textfield. There are two fields - From and To. When the "Add New Field" button is clicked, it generates two new fields. These two are part of the state object. Now, if the user enters a value in "To" field which is lesser than "from" field, it's supposed to display an error below the field as defined in the 'helper text'. However, in my case, the error appears in all the 'To' fields even though the error is supposed to appear only in the row where the input is wrong. It's repeating it in all the rows. How do I fix this?
The code is as follows. It can be reproduced in sandbox directly.
import "./styles.css";
import React from "react";
import { Button, Grid, Paper } from "#mui/material";
import { TextField } from "#mui/material";
interface Props {}
interface State {
serialInputObjects: any;
}
var fromErrorMessage = "";
var toErrorMessage = "";
class SerialQRScanClass extends React.PureComponent<Props, State> {
state = {
serialRegistrationTracker: [],
serialInputObjects: {
//0: { from: "", to: "", except: "" } }
}
};
calculation = (key) => {
let errors = this.getFromToSerialErrorMessages(
this.state.serialInputObjects[key]["from"],
this.state.serialInputObjects[key]["to"]
);
fromErrorMessage = errors.fromErrorMessage;
toErrorMessage = errors.toErrorMessage;
console.log(`Key ${key} From error message - ` + fromErrorMessage);
console.log("To error message - " + toErrorMessage);
};
getSerialCodeErrorMessage = (serialCode) => {
if (!serialCode) return "";
if (String(serialCode).match(/[^0-9,]+/)) {
return "Enter only numbers";
}
return "";
};
getFromToSerialErrorMessages = (fromSerial, toSerial) => {
const fromErrorMessage = this.getSerialCodeErrorMessage(fromSerial);
let toErrorMessage = this.getSerialCodeErrorMessage(toSerial);
if (!fromErrorMessage && !toErrorMessage) {
const diff = parseInt(toSerial) - parseInt(fromSerial);
if (diff < 0) toErrorMessage = "To lower than starting point";
}
return { fromErrorMessage, toErrorMessage };
};
handleAdd = () => {
const objectLength = Object.keys(this.state.serialInputObjects).length;
console.log(objectLength);
this.setState((prevState) => ({
...prevState,
serialInputObjects: {
...prevState.serialInputObjects,
[objectLength]: { from: "", to: "", except: "" }
}
}));
console.log(this.state.serialInputObjects);
};
handleChangeFromSerials = (key: any, data: string) => {
this.setState((prevState) => ({
...prevState,
serialInputObjects: {
...prevState.serialInputObjects,
[key]: { ...prevState.serialInputObjects[key], from: data }
}
}));
console.log(this.state.serialInputObjects);
this.calculation(key);
};
handleChangeToSerials = (key: any, data: string) => {
this.setState((prevState) => ({
...prevState,
serialInputObjects: {
...prevState.serialInputObjects,
[key]: { ...prevState.serialInputObjects[key], to: data }
}
}));
console.log(this.state.serialInputObjects);
this.calculation(key);
};
render() {
return (
<Paper elevation={3} className="abc">
<Button onClick={this.handleAdd}>ADD NEW FIELD</Button>
{Object.keys(this.state.serialInputObjects).map((key) => (
<div key={key}>
<Grid container alignItems="flex-end">
<Grid item className="bcd">
<TextField
fullWidth
label={"FROM"}
placeholder={"Ex.100"}
value={this.state.serialInputObjects[key]["from"]}
onChange={(e) =>
this.handleChangeFromSerials(key, e.target.value)
}
error={Boolean(fromErrorMessage) || false}
helperText={fromErrorMessage}
margin="none"
size="small"
/>
</Grid>
<Grid item className="bcd">
<TextField
fullWidth
label={"To"}
placeholder={"Ex.100"}
value={this.state.serialInputObjects[key]["to"]}
onChange={(e) =>
this.handleChangeToSerials(key, e.target.value)
}
error={Boolean(toErrorMessage) || false}
helperText={toErrorMessage}
margin="none"
size="small"
/>
</Grid>
</Grid>
</div>
))}
</Paper>
);
}
}
export default function App() {
return (
<div className="App">
<SerialQRScanClass />
</div>
);
}
I want to be able to print the error only in that corresponding field in the loop.
We can do this by tracking all the errors for every individual input. First update the state with a fromErrorMessage and a toErrorMessage object. These will hold the errors for the inputs.
state = {
serialRegistrationTracker: [],
serialInputObjects: {
//0: { from: "", to: "", except: "" } }
},
fromErrorMessage: {},
toErrorMessage: {},
};
Then we can update the calculation function to store the errors for the specific input. I added 2 new arguments fromValue and toValue these will help with checking the up-to-date value and prevent the error messages to be one state behind.
calculation = (key, fromValue, toValue) => {
let errors = this.getFromToSerialErrorMessages(fromValue, toValue);
this.setState((prevState) => ({
...prevState,
fromErrorMessage: {
...prevState.fromErrorMessage,
[key]: errors.fromErrorMessage,
},
toErrorMessage: {
...prevState.toErrorMessage,
[key]: errors.toErrorMessage,
},
}));
console.log(
`Key ${key} From error message - ` + this.state.fromErrorMessage[key]
);
console.log("To error message - " + this.state.toErrorMessage[key]);
};
Now we need to update the handlers to work with the new calculation function. We pass the current state from the to and the new data to check against and vice versa.
handleChangeFromSerials = (key: any, data: string) => {
...
this.calculation(key, data, this.state.serialInputObjects[key]["to"]);
};
handleChangeToSerials = (key: any, data: string) => {
...
this.calculation(key, this.state.serialInputObjects[key]["from"], data);
};
Finally update the TextField components. And the same for the to input.
<TextField
...
error={Boolean(this.state.fromErrorMessage[key]) || false}
helperText={this.state.fromErrorMessage[key]}
...
/>
I am trying to make field validation and I am stuck in the case of comparing 2 element values in the same array of objects. Could someone explain me how generally it is done? I want password and repPassword values to be compared in one iteration.
export const fieldsConfig = [
{
id: "email",
label: "email",
value: "",
error: false,
helperText: "email must includes chars: # and .",
},
{
id: "userName",
label: "user name",
value: "",
error: false,
helperText: "user name must be min 5 char",
},
{
id: "password",
label: "password",
value: "",
error: false,
helperText: "password must be min 5 chars",
},
{
id: "repPassword",
label: "rep-password",
value: "",
error: false,
helperText: "passwords not mutch",
},
];
Here is my redux state where I am trying to check fields value and make validation for each one:
case FIELDSVALIDATION:
const validationFields = [...state.fields].map((element, index, arr) => ({
id: element.id,
label: element.label,
value: element.value,
error:
element.id === "email" &&
!/(.+)#(.+){2,}\.(.+){2,}/.test(element.value)
? (element.error = true)
: element.value.length < 5
? (element.error = true)
: (element.error = false),
helperText: element.helperText,
}));
return {
...state,
fields: validationFields,
}
I'm using input field form from material UI. Here is part of a component and how I implement it:
const state = useSelector((state) => state.regReducer);
const dispatch = useDispatch();
return (
<div className="registration">
{state.fields.map((element, key) => {
return (
<TextField
key={key}
id={element.id}
label={element.label}
value={element.value}
error={element.error}
helperText={element.error ? element.helperText : ""}
onChange={(e) =>
dispatch(onChangeHandler(e.target.id, e.target.value))
}
/>
);
})}
You can try adding a custom function which searches for the fields in the state and compares their current value:
<div className="registration">
{state.fields.map((element, key) => {
const generateError = () => {
if (element.id.includes("password")) {
const password = state.fields.find(el => el.id == "password");
const repPassword = state.fields.find(el => el.id == "repPassword");
if (password.value != repPassword.value) {
return true;
} else {
return false;
}
} else return element.error;
};
return (
<TextField
key={key}
id={element.id}
label={element.label}
value={element.value}
error={generateError}
helperText={element.error ? element.helperText : ""}
onChange={e =>
dispatch(onChangeHandler(e.target.id, e.target.value))
}
/>
);
})}
</div>
I have a complex data set, so I will show a very simplified version for an example.
Input data:
const data = [
{
type: "input",
caption: "Name",
defaultValue: "John Smith"
},
{
type: "input",
caption: "Name",
defaultValue: "John Smith"
},
{
type: "input",
caption: "Name",
defaultValue: "John Smith"
},
{
type: "input",
caption: "Name",
defaultValue: "John Smith"
},
{
type: "input",
caption: "Name",
defaultValue: "John Smith"
}
];
Each item of the array is removable. It turns out something like this.
There are several conditions. I should not modify the data array so i create a deep copy. As well inside the copy i can only delete elements but don't modify their properties. Thus each element has to have local state with a new value.
Working example:
function App() {
const [mainData, setMainData] = useState(deepCopy(data));
return (
<React.Fragment>
{
mainData.map((item, i) => {
return (
<Input {...item} key={i} num={i} setMainData={setMainData}/>
)
})
}
</React.Fragment>
)
}
const Input = (props) => {
const [value, setValue] = useState(props.defaultValue);
const deleteElem = () => {
props.setMainData((mainData) => {
return [...mainData.filter((_, ind) => ind !== props.num)];
});
};
return (
<div>
<div>
<div>{`${props.caption}:`}</div>
<input value={value} onChange={(e)=>setValue(e.target.value)}/>
</div>
<button onClick={deleteElem}>delete</button>
</div>
)
};
const deepCopy = (aObject) => {
if (!aObject) {
return aObject;
}
let value;
let bObject = Array.isArray(aObject) ? [] : {};
for (const key in aObject) {
value = aObject[key];
bObject[key] = (typeof value === "object") ? deepCopy(value) : value;
}
return bObject;
};
If you try to delete not the last element then (because of the keys) the values of the inputs elements will be mixed up.
What can I do about it?
With deepCopy you can add a unique id to each item when you initialize your state. Once you do that you can leverage that id for passing as key to the Input element
import {uuid} from 'uuidv4';
function deepCopyAndAddId = () => {
let newData = deepCopy(data);
newData = newData.map((item, index) => ({...item, id: uuid()}));
}
function App() {
const [mainData, setMainData] = useState(deepCopyAndAddId);
return (
<React.Fragment>
{
mainData.map((item, i) => {
return (
<Input {...item} key={item.id} num={i} setMainData={setMainData}/>
)
})
}
</React.Fragment>
)
}
To make minimum changes in your code - just never delete the item in deleteElem, but add a flag deleted to it instead.
When render an item, show <Fragment> for the deleted item.
I change the name and age dynamiclly by creating two handlers. When I input an age it changes dynamicly. There is no problem here. But the name goes default as well. Because I defined it as a hard-coded value in 'ageChangeHandler'.But i want the name to stay like I did in the 'nameChangeHandler'. Is there something like 'currentValue' or 'lastValue' in Javascript/ES6?
I hope I explained it properly.
Thanks in advance.
I couldnt find any options like currentvalue etc.
nameChangeHandler = (event) =>{
this.setState({
persons: [
{name: "Max" , age: 28},
{name: event.target.value, age: 29},
{name:"Arthur", age:34}
]
})
}
ageChangeHandler = (event) =>{
this.setState({
persons:[
{name: "Max" , age: 28},
{name: "Tom", age: 29},
{name:"Arthur", age:event.target.value}
]
})}
Lines I call handlers:
<Person
name={this.state.persons[0].name}
age={this.state.persons[0].age}/>
<Person
name={this.state.persons[1].name}
age={this.state.persons[1].age}
click={this.switchNameHandler.bind(this, "Way 2")}
Namechanged={this.nameChangeHandler}>My hobbies: coding</Person>
<Person
name={this.state.persons[2].name}
age={this.state.persons[2].age}
Agechanged={this.ageChangeHandler}/>
If you pass a function, instead of an object to setState, you get this signature:
this.setState((prevState, props) => {
// do something with prevState
return { ...prevState }
});
allowing you to use the previous state to create the new one.
so In your case:
this.setState(prevState => {
return {
...prevState,
persons: prevState.persons.map(person => {
if (person.name === "Arthur") return { ...person, age: e.target.value }
return person
})
}
})
or something like that.
You can use an index to dynamically select a user to edit and then just update its attributes:
nameChangeHandler = (event) => {
const { index } = this.state;
this.setState({
persons: this.state.persons.map((item, j) => {
if (j === index) {
return {
...item,
name: event.target.value,
};
} else {
return item;
}
}),
});
}
You could also create a universal function to update any number of parameters:
<input
type="text"
// Here we send a value, n index (can be from state) and the parameter to update
onChange={(e) => { this.changeHandler(e.target.value, 0, 'name') }}
/>
changeHandler = (value, index, attr) => {
// index is the object in the array that we want to update
// attr is the key
this.setState({
persons: this.state.persons.map((item, j) => {
if (j === index) {
return {
...item,
[attr]: value,
};
} else {
return item;
}
}),
});
}
Declared an ID for all of persons :
state = {
persons: [
{id:"25343", name:"Max", age: 28},
{id:"24323ad ", name: "Tom", age: 29},
{id:"asdavd231", name:"Arthur", age:34}
],
Updated them with their ID's :
ageChangeHandler = (event,id) =>{
const personIndex = this.state.persons.findIndex(p=>{
return p.id === id;
})
const person = {
...this.state.persons[personIndex]
}
person.age = event.target.value;
const persons = [...this.state.persons]
persons[personIndex] = person;
this.setState({persons:persons})}
Rendered them in a map:
<div>
{this.state.persons.map((person,index)=>{
return <Person
click={() => this.deletePersonHandler(index)} //map'ten gelen index deletePersonHandler'a pareametre olarak gönderiliyor
name={person.name} //map'in 1.parametresi. (persons'u işaret ediyor)
age={person.age}
key={person.id}
nameChanged={(event)=>this.nameChangeHandler(event,person.id)}
ageChanged={(event)=>this.ageChangeHandler(event,person.id)}/>
})}
</div>
I have a small problem with my dynamic form. In the code below the render method I have code that maps an input and a dropdown select menu to fill in state.questions[{title: "", type: ""}]. You can see the addQuestionsHandler method to add more questions and the questionsInputHandler to handle the questions values.
The surveyInputHandler method handles the static questions in the return function.
The problem I'm having is that in my code for the dynamic questions the input value and the select dropdown value are ending ending up the same in state.questions[{title: "", type: ""}]. If I input "Test" - both title and type will be "Test". If I input "Test" and select value = "Radio Button" - both title and type will be "Radio Button". If I don't select a dropdown option value, then both will be the input value. If I do select a dropdown option value then the input value will be overridden by the dropdown select value.
I've racked my brain for a while but I need more eyes on it. Can you please let me know what I'm not doing correctly? Thanks so much.
const questionTypes = [
"Select Question Type",
"Textbox",
"Radio Button",
"Checkbox"
];
class SurveyQuestions extends Component {
constructor(props) {
super(props);
this.state = {
title: "",
description: "",
pointsValue: 0,
questions: [
{
title: "",
type: ""
}
]
};
}
surveyInputHandler = e => {
console.log(e.target.value);
this.setState({
[e.target.name]: e.target.value,
[e.target.title]: e.target.value,
[e.target.description]: e.target.value,
[e.target.pointsValue]: e.target.value
});
};
questionsInputHandler = idx => e => {
console.log(e.target.value);
const newQuestions = this.state.questions.map((question, qidx) => {
if (idx !== qidx) return question;
return {
...question,
title: e.target.value,
type: e.target.value
};
});
this.setState({
questions: newQuestions
});
};
addQuestionHandler = () => {
this.setState(prevState => ({
questions: [...prevState.questions, { title: "", type: "" }]
}));
};
submitHandler = e => {
const { title, description, pointsValue, questions } = this.state;
console.log(
title,
description,
pointsValue,
questions.map(question => ({ ...question }))
);
this.setState({
title: "",
description: "",
pointsValue: "",
questions: [{ title: "", type: "" }]
});
e.preventDefault();
};
render() {
const { title, description, pointsValue, questions } = this.state;
const questionsDisplay = questions.map((question, idx) => (
<div key={idx} className="SurveyQuestions__QuestionContainer">
<h5>Question {idx + 1}</h5>
<label htmlFor="questionTitle">Question Title</label>
<input
type="text"
id="questionTitle"
placeholder={`Question Title #${idx + 1}`}
value={question.title}
onChange={this.questionsInputHandler(idx)}
/>
<label htmlFor="questionType">Type</label>
<select
className="SurveyQuestions__QuestionTypesDropdown"
value={question.type}
onChange={this.questionsInputHandler(idx)}
>
{questionTypes.map((type, tidx) => (
<option key={tidx} id={`${type}-${tidx}`} value={type}>
{type}
</option>
))}
</select>
</div>
));
Solved:
So the simple solution was to create a separate input handler for the select dropdown menu. The code is below:
questionTypeInputHandler = idx => e => {
const newQuestions = this.state.questions.map((question, qidx) => {
if (idx !== qidx) return question;
return {
...question,
type: e.target.value
};
});
this.setState({
questions: newQuestions
});
};