I'm working on a React app and want the form I'm working on to be able to add key name and key value to sensorProperties: {} (sensorProperties is a part of sensor object). I have to allow user to give a name for both, key and value.
this.state = {
sensor: {
name: '',
type: '',
position: '',
sensorProperties: {}
},
show: false,
key: '',
value: ''
};
I added two functions handleValueChange and handleKeyChange. When I invoke console.log(key, value), I see they have my input values, but how can I pass these values to my sensorProperties?
handleKeyChange = (event) => {
const nKey = event.target.value
this.setState({key: nKey})
}
handleValueChange = (event) => {
const nValue = event.target.value
this.setState({value: nValue})
}
<FormGroup>
<ControlLabel>Key</ControlLabel>
<FormControl required type="text" value={key}
onChange={this.handleKeyChange}/>
</FormGroup>
<FormGroup>
<ControlLabel>Value</ControlLabel>
<FormControl required type="text" value={value}
onChange={this.handleValueChange}/>
</FormGroup>
You can change the handlers and set the sensorProperties like this:
handleKeyChange = (event) => {
const nKey = event.target.value
const value = this.state.value;
const newSensor = { ...this.state.sensor };
const newSensorProperties = {
[nKey]: value, // dynamically set the key name
};
newSensor.sensorProperties = newSensorProperties;
this.setState({
sensor: newSensor,
key: nKey,
});
}
handleValueChange = (event) => {
const nValue = event.target.value
const key = this.state.key;
const newSensor = { ...this.state.sensor };
const newSensorProperties = {
[key]: nValue, // dynamically set the key name
};
newSensor.sensorProperties = newSensorProperties;
this.setState({
sensor: newSensor,
value: nValue,
});
}
Depends on your need, the method above will only set one key-value pairs, and it also accepts an empty string as the key of the sensorProperties. If it is not want you want, you can just add an additional checking for empty string. The main idea is to use the computed key names for the newSensorProperties and that should solve the problem.
Related
when I console log the value of dob, its an object instead of a string
initial state
const initialValues = {
registrationForm: {
dob: {
elementType: 'date',
elementConfig: {
name: 'dob',
required: true,
placeholderText: 'Date of birth'
},
value: null,
},
},
}
state*
const [values, setValues] = useState(initialValues);
updateObject
export const updateObject = (oldObject, updatedProperties) => {
return {
...oldObject,
...updatedProperties
};
};
handle change handler
const handleChange = (event, inputIdentifier, config) => {
const updatedRegistrationForm = updateObject(values.registrationForm, {
[inputIdentifier]: updatedFormElement
});
setValues({registrationForm: updatedRegistrationForm);
};
handle submit*
const handleSubmit = (event) => {
event.preventDefault()
const formData = {};
for (let formElementIdentifier in values.registrationForm) {
formData[formElementIdentifier] = values.registrationForm[formElementIdentifier].value;
}
console.log(formData)
};
for (let key in values.registrationForm) {
formElementsArray.push({
id: key,
config: values.registrationForm[key]
});
}
form
<form onSubmit={handleSubmit}>
{
formElementsArray.slice(sliceStart, sliceNumber).map(({config, id}) => {
return <Input
key={id}
elementType={config.elementType}
elementConfig={config.elementConfig}
value={config.value}
changed={(event) => handleChange(event, id)}/>
})
}
</form>
input
const Input = ({
elementType,
elementConfig,
value,
changed,
}) => {
let inputElement = null;
switch (elementType) {
case ('date'):
inputElement =
<DatePicker
id={id
className='mb-3 form-control'
{...elementConfig}
selected={value}
onChange={changed}/>
break;
}
return (
<>
{inputElement}
</>
);
};
export default Input;
I was expecting something similar to this when I console log(Stringvale)
dob: "2023-01-11T21:00:00.000Z"
but I got this(an object)
Any help will be highly appreciated.
<DatePicker
id={id
className='mb-3 form-control'
{...elementConfig}
selected={value}
onChange={changed}/>
onChange in DatePicker returns a date object. You can use any date formatters to convert the date object to required format.
Update changed method to accept the date object, do the conversions like below.
const handleChange = (event, inputIdentifier, config) => {
const updatedRegistrationForm = updateObject(values.registrationForm,
{
[inputIdentifier]: updatedFormElement
});
if(inputIdentifier=="dob") {
updatedFormElement.value=formatDate(date); //create some function to do the date formatting
} else {
updatedFormElement.value = event.target.value // for example
// logic for non date inputs
}
setValues({registrationForm: updatedRegistrationForm);
};
I am trying to create some custom error validation in React
I have a values obj in state and an error obj in state that share the same keys
const [values, setValues] = useState({
name: "",
age: "",
city: ""
});
const [err, setErr] = useState({
name: "",
age: "",
city: ""
});
i have a very simple handle change and an onSubmit which i want to run my custom validator function inside
const handleChange = (e) => {
setValues({
...values,
[e.target.name]: e.target.value
});
};
const handleSubmit = (e) => {
e.preventDefault();
validateForms();
};
in my validateForms function my theory is since both my pieces of state share the same keys I am trying to see if any of those values === '' if yes match is the same key in the err obj and set that respective value to the error and then do other stuff in JSX
const validateForms = () => {
for (const value in values) {
if (values[value] === "") {
setErr({
...err,
value: `${value} is a required field`
});
}
}
};
I definitely feel like I'm not using setErr properly here. Any help would be lovely.
link to sandbox: https://codesandbox.io/s/trusting-bartik-6cbdb?file=/src/App.js:467-680
You have two issues. First, your error object key needs to be [value] rather than the string value. Second, you're going to want to use a callback function in your state setter so that you're not spreading an old version of the error object:
const validateForms = () => {
for (const value in values) {
if (values[value] === "") {
setErr(err => ({
...err,
[value]: `${value} is a required field`
}));
}
}
};
A more intuitive way to set errors might be to accumulate them all and just set the error state once:
const validateForms = () => {
const errors = {};
for (const value in values) {
errors[value] = values[value] === "" ? `${value} is a required field` : "";
}
setErr(errors);
};
In the following code everything works except for the update of the value in the onChange method.
The expected way it should work is:
initial value is an empty string (✓ works)
when a change is made the value should be the value of the change (✗ does not work)
const mockSetFieldValue = jest.fn(() => '');
beforeAll(async () => {
field = {
name: 'password',
value: mockSetFieldValue(),
// ^^^ initial value is picked up, but not the change in onChange
onChange: (e) => {
console.log(e.target.value) // returns: foo123
mockSetFieldValue.mockReturnValue(() => e.target.value);
// ^^^ this does not update the value
},
};
tree = (
<>
<label htmlFor="password">Password</label>
<MyField field={field} />
</>
);
});
it('input accepts a value', () => {
const { getByLabelText } = render(tree);
const input = getByLabelText(/Password/i);
fireEvent.change(input, { target: { value: 'foo123' } });
expect(input.value).toBe('foo123');
});
How would it be possible to update the onChange method to change the value that is set in my component?
I've tried mockImplementationOnce and mockReturnValue. But they don't seem to work that way.
field.value is set once before all your tests using the current value of the mockSetFieldValue function, which returns the empty string. Changing the mockSetFieldValue function therefore has no effect.
You need to be able to set the field.value in onChange, which you could do like this:
onChange: (e) => {
field.value = e.target.value;
},
It is probably better to mock out the whole onChange function:
const mockOnChange= jest.fn();
beforeAll(async () => {
field = {
name: 'password',
value: '',
onChange: mockOnChange,
};
//...
});
Then in the test:
mockOnChange.mockImplementationOnce((e) => { field.value = e.target.value });
fireEvent.change(input, { target: { value: 'foo123' } });
I have attributes in the state, I would like to ensure that by specifying the function the attribute name changes the value contained in the state.
It seems to work, the problem that if I have an object of this type in the state:
companyInfo: {
name: "",
vatNumber: "",
legalRepresentative: ""
}
It does not work properly, as the code is now set in the state in this case a new attribute is created.
So I'd like to do something like this:
handleChangeField("companyInfo.name")
It is changed to the state atrribute name of the obj companyInfo that is in the state.
Can you give me some advice?
Link: codesandbox
Code:
import ReactDOM from "react-dom";
import React, { Component } from "react";
import ReactJson from "react-json-view";
class Todo extends Component {
constructor(props) {
super(props);
this.state = {
email: "email0",
role: "role0",
companyInfo: {
name: "",
vatNumber: "",
legalRepresentative: ""
}
};
}
returnStateElement = (...elements) => {
const copy = Object.assign({}, this.state);
return elements.reduce((obj, key) => ({ ...obj, [key]: copy[key] }), {});
};
handleChangeField = field => evt => {
let state = {};
state[field] = evt.target.value;
this.setState(state);
};
handleSubmit = () => {
let el = this.returnStateElement(
"name",
"email",
"vatNumber",
"legalRepresentative",
"role"
);
let { name, email, legalRepresentative, vatNumber, role } = el;
let dataSender = {};
dataSender.email = email;
dataSender.role = role;
dataSender.companyInfo = {};
dataSender.companyInfo.name = name;
dataSender.companyInfo.legalRepresentative = legalRepresentative;
dataSender.companyInfo.vatNumber = vatNumber;
console.log(this.state);
//console.log(dataSender)
};
render() {
return (
<div>
<input onChange={this.handleChangeField("email")} />
<br />
<br />
<input onChange={this.handleChangeField("companyInfo.name")} />
<br />
<br />
<button onClick={() => this.handleSubmit()}>send</button>
<br />
<br />
<ReactJson src={this.state} theme="solarized" />
</div>
);
}
}
ReactDOM.render(<Todo />, document.getElementById("root"));
Edit: I came up with a much better answer where one mutates the specific key of the oldState using a reduce. Less code, much more elegant and should work at any object depth.
Working example here
setNestedField(object, fields, newValue) {
fields.reduce((acc, field, index) => {
if (index === fields.length - 1) {
acc[field] = newValue;
}
return acc[field];
}, object);
return object;
}
handleChangeField = field => evt => {
const fields = field.split(".");
let oldState = this.state;
const newState = this.setNestedField(
{ ...oldState },
fields,
evt.target.value
);
this.setState(newState);
};
OLD ANSWER
handleChangeFields looks like this:
handleChangeField = field => evt => {
//first you split by '.' to get all the keys
const fields = field.split(".").reverse();
// you'll need the previous state
let oldState = this.state;
let newState = fields.reduce((acc, value, index) => {
if (index === 0) {
// you add the event value to the first key
acc[value] = evt.target.value;
return acc;
}
//copy acc to use it later
const tmp = { ...acc };
//delete previous key added to acc
delete acc[fields[index - 1]];
acc[value] = { ...oldState[value], ...tmp };
return acc;
}, {});
this.setState(newState);
};
What's going on step by step in the reduce function, if you do handleChangeField('company.name') with evt.target.value = "Big Corp":
1) you get the array ['name','company']
2) you go in the reduce function
when index = 0, acc = {}, key='name' => {name: 'Big Corp'}
when index=1, acc= {name: 'Big Corp'},key='company' => acc = { company: {name: 'Big Corp'}, name: 'BigCorp} so before returning we delete the previous key (name here) to return => { company: {name: 'Big Corp'}
I have a TextField.
this is my structure data
Tour {
id="";
name="";
description="";
lenghts = {
byDay : ""
,byHour : ""
}
}
I use the function to daynamic set value to state.
handleChange (event) {
event.preventDefault();
var field = event.target.name;
var value = event.target.value;
this.setState({
[field]: value
});
};
for use this function i must use name="lenghts.byDay" to dynamic set value.
<TextField
className={styles.textField}
label="Day"
type="text"
name="lenghts.byDay"
onChange={this.handleChange}
margin="normal"
value = {this.state.lenghts.byDay}
/>
My problem is:
when i use this plan,i can't type any character in TextField!
because the name of TextField is invalid.
What is my solution?
You can do this as follows:
<TextField
className={styles.textField}
label="Day"
type="text"
onChange={this.handleChange.bind(this, 'lenghts.byDay'} margin="normal" />
In your function handler:
handleChange (field, event) {
event.preventDefault();
var value = event.target.value;
this.setState({
[field]: value
});
};
Hope it could help
my problem is solved
Thank a lot to All,
Special to #vitomadio
based my plan:
handleChange (event) {
event.preventDefault();
var field = event.target.name;
var value = event.target.value;
this.setState({
[field]: value
});
};
currently,setstate to lenghts.byDay: "20" and the initial state is this.state.lenghts.byDay
when i type character to input,this.state.lenghts.byDay replaced in value
this is reason for i can't type any character.
I change the plan in handleChange (event).
handleChange(event) {
console.log('handleChange');
event.preventDefault();
var field = event.target.name;
var value = event.target.value;
if (field.includes('.')) {
var fieldSplit = field.split(".");
var field0=fieldSplit[0];
var field1=fieldSplit[1];
this.setState(prevState => (
{
...prevState,
[field0]:{...prevState[field0],[field1] : value}
}
));
} else {
this.setState({
[field]: value
});
}
};
and now setSate data to
lenghts {
byDay: "20"
}