I've got two select components and I want to set values from this selectors to state using one handleChange method. I've tried to do like this:
handleChange = event => {
const { name, value } = event.target;
if (name === 'City') {
this.setState({ selectedCity: value });
} else if (name === 'Theatre') {
this.setState({ selectedTheatre: value });
}
};
But I want more universal solution, like I did with inputs:
handleChange = event => {
const { name, value } = event.target;
this.setState({
[name]: value
});
};
My state
this.state = {
cities: [],
theatres: [],
movies: [],
selectedCity: 1,
selectedTheatre: 1
};
at the first, the name for each input must look like the element in the state
ex
tell me
handaleChange=(event)=>{
this.setState({
[event.name]=event.target.value
}
}
tell me if it work or not
make Handel different for selected Combobox element different
handelSelectedElement=(event)=>{
const value=event.target.checked
const name:event.target.name;
let cityselected=Object.assign({},this.state.city,{[name]:value})
this.setState({city:cityselected})}
tell me if work or not
Ensure your dropdown list name matches the state name
this.state = {
cities: [],
theatres: [],
movies: [],
selectedCity: 1,
selectedTheatre: 1
};
<select name="selectedCity"
value={this.state.selectedCity}
onChange={this.handleChange }>
</select>
<select
name="selectedTheatre"
value={this.state.selectedTheatre}
onChange={this.handleChange }>
</select>
handleChange = event => {
const target = event.target
const value = target.type === 'checkbox' ? target.checked : target.value
const name = target.name
this.setState({
[name]: value
});
};
Note: Dont forget to copy your event data into a variable or call event.persist() to avoid issues.
See link [Event target is null inside functional setState
Related
What I'm trying to do here is to build a dynamic component, that will be responsible to take an array of objects and the form will be built based on my formState. So I made my initial State and used .map to make the loop over the state and mapped the keys to making the label, value, and inputs appear based on the state. but my problem is at onChange. How to update the value key in every object and set the new state for it. any advice, please.
import { useState } from "react";
import InputText from "./components";
import useForm from "./hooks/useForm";
function App() {
interface formStateT {
id: number;
label: string;
value: any;
error: string;
}
const formState = [
{
id: 0,
label: "firstName",
value: "",
error: "",
},
{
id: 1,
label: "lastName",
value: "",
error: "",
},
];
const { form, validate, setForm, checkValidHandler } = useForm(formState);
const [error, setError] = useState("");
const submitFormHandler = (e: { preventDefault: () => void }) => {
e.preventDefault();
checkValidHandler();
// write form logic
// setError() will be used to take the error message
console.log(form);
};
return (
<form onSubmit={(e) => submitFormHandler(e)}>
{form.map((f: formStateT) => (
<InputText
key={f.id}
label={f.label}
value={f.value}
onChange={(e) => {
// Im trying here to update the value key of every label key.
// setForm({ ...form, [f.label.valueOf()]: f.value })
}}
valid={f.value === "" ? validate.notValid : validate.valid}
errorMsg={error === "" ? f.error : error}
classes={"class"}
/>
))}
<button>Submit</button>
</form>
);
}
export default App;
From your comment, f.value = e.target.value; is a state mutation and should be avoided, the setForm([...form]); is masking the mutation.
In App create an onChangeHandler function that takes the onChange event object and the index you want to update. Unpack the value from the onChange event and update the state. The handler should use a functional state update to update from the previous state, and create a shallow copy of the form array, using the index to shallow copy and update the correct array element.
Example:
// curried function to close over index in scope, and
// return handler function to consume event object
const onChangeHandler = index => e => {
const { value } = e.target;
setForm(form => form.map((el, i) =>
i === index
? { ...el, value }
: el
));
};
...
<form onSubmit={submitFormHandler}>
{form.map((f: formStateT, index: number) => (
<InputText
key={f.id}
label={f.label}
value={f.value}
onChange={onChangeHandler(index)} // <-- invoke and pass mapped index
valid={f.value === "" ? validate.notValid : validate.valid}
errorMsg={error === "" ? f.error : error}
classes={"class"}
/>
))}
<button>Submit</button>
</form>
Initial state
super(props);
this.state = {
user: {
id: 0,
firstName: '',
lastName: '',
phoneNumbers: ['', '', ''],
}
};
}
my handleInputChange function:
handleInputChange = (event) => {
const { user } = this.state;
const name = event.target.name;
const value = event.target.value;
if (name === 'contactNumber1') {
user.phoneNumbers[0] = value;
} else if (name === 'contactNumber2') {
user.phoneNumbers[1] = value;
} else if (name === 'contactNumber3') {
user.phoneNumbers[2] = value;
console.log('hellooo', user.phoneNumbers);
} else {
user[name] = value;
}
const filteredPhoneNumbers = user.phoneNumbers.filter(e => e !== '');
this.setState(prevState => ({
user: {
...prevState.user,
[name]: value,
phoneNumbers: [...filteredPhoneNumbers],
},
}));
}
When I remove an element, the element in the index above populates the element I just deleted What am I missing?
* I am getting this error because of my code too - A component is changing a controlled input of type undefined to be uncontrolled. Input elements should not switch from controlled to uncontrolled (or vice versa). Decide between using a controlled or uncontrolled input element for the lifetime of the component*
You could try parsing "this.state" like this:
const { user } = JSON.parse(JSON.stringify(this.state));
I think you're referencing the same object to the user cons
I have a list of columns, they are rendered as TextFields:
{columns.map((column, index) => (
<TextField
key={index}
margin="dense"
id={column}
label={column}
name={column}
type="text"
onChange={this.handleInputChange}
fullWidth/>
))}
and handleInputChange function is written as below:
handleInputChange(event) {
const target = event.target;
const value = target.value;
const name = target.name;
this.setState({
addData: {
[this.state.fullName]:{
[name]: value
}
}
});
}
The data that I want to receive is:
addData: {
[name-of-column-1]: value,
[name-of-column-2]: value,
[name-of-column-3]: value,
.......
}
but the handleInputChange function overrides after every TextField change, the data that I received:
addData: {
[name-of-column-1 (or 2, 3...)]: value,
}
Is there any way to get the data I need? Thanks everyone!
How do I setState for nested object?
handleInputChange(event) {
const target = event.target;
const value = target.value;
const name = target.name;
this.setState({
addData: {
[this.state.fullName]:{
...this.state.addData[this.state.fullName],
[name]: value,
}
}
});
}
You are assigning whole new object every time value from one TextField changes and not retaining old values from state.
I'm not sure what exactly is this.state.fullName, but in order to have your desired state structure you can implement it like this:
handleInputChange(event) {
const target = event.target;
const value = target.value;
const name = target.name;
this.setState({
addData: {
...this.state.addData,
[name]: value
}
});
}
When destructing this.state.addData (with ...), you are basically assigning all of its properties to the new object and then adding additional [name]: value. Its important to put destruction above new assignment in order to update latest value.
I think you should prepare your data first and set it into your state or try in this way
this.setState({
addData: {
[...this.state.fullName]:{
[name]: value
}
}
});
This's a issue when you setState. When you using
this.setState({
addData: {
[this.state.fullName]:{
[name]: value
}
}
});
that mean you are setting a new state just have one nested object
addData { abc: value }
so the old values are lost. You need add them before you change the new value. Try this
handleInputChange(event) {
const { name, value } = event.target;
const newData = {...this.state.addData};
newData[name] = value;
this.setState({ addData: newData });
}
or
handleInputChange(event) {
const { name, value } = event.target;
this.setState({
addData: {
...this.state.addData,
[name]: value
}
});
}
I have a set of results from an api, stored in the state as the array 'results'.
I want a second array, 'visible_results' in the state. This should be a subset of 'results'. I'm trying to do this like this:
export default class SearchScreen extends Component {
constructor(props) {
super(props);
this.state = {
results: null,
visible_results: null,
low_price: null,
high_price: null,
min_price: null,
max_price: null
};
}
componentDidMount() {
const apiUrl = 'foo';
fetch(apiUrl)
.then(response => response.json())
.then(response => {
this.setState({
results: response.results,
min_price: 1,
max_price: 100
});
this.setState({
low_price: this.state.min_price,
high_price: this.state.max_price
});
});
}
handleChange = event => {
const { name, value } = event.target;
this.setState({
[name]: +value
});
this.setState({
visible_results: this.state.results.reduce((items, x) => {
if (this.state.low_price <= x.price && x.price <= this.state.high_price)
items.push(x);
return items;
})
});
};
The handleChange is tied to two sliders, one setting low_price, and one setting high_price. The function should then generate a subset of results, based on the new value of low_price or high_price, and save it to the state as visible_results.
It doesn't work. There are no errors, but visible_results always remain 'null'. The sliders definitely work. I've tried replacing the if statement with if (1==1) to make sure that it wasn't just an if statement typo. It did the same thing.
A few things:
Setting the values to null makes your code more complicated, I would either add a useful default value, e.g. min_price: 0, max_price: Infinity or just don't initialize it.
this.setState is asynchronous! If you call setState twice, the first call will be deferred, so this.state isn't yet update in the second call, so e.g. this.state.low_price inside the second call in handleChange wasn't yet updated.
If you don't pass an initial value to reduce, it will take the arrays first element, which in your case is an object, calling push on this won't work. You probably want .reduce(fn, []), but in your case .filter is actually more appropriate.
visible_results doesn't have to be part of the state as it is determined by other state props, so just determine it on render
Code:
export default class SearchScreen extends Component {
constructor(props) {
super(props);
this.state = {
results: [],
low_price: 0,
high_price: Infinity,
min_price: 0,
max_price: Infinity
};
}
componentDidMount() {
const apiUrl = 'foo';
fetch(apiUrl)
.then(response => response.json())
.then(response => {
this.setState({
results: response.results,
min_price: 1,
max_price: 100,
min_price: 1,
high_price: 100,
});
});
}
handleChange = event => {
const { name, value } = event.target;
this.setState({
[name]: +value
});
}
render() {
const { results, low_price, high_price } = this.state;
const visibleResults = results.filter(item =>
low_price <= item.price && item.price <= high_price);
//...
}
}
My parent component has a property in its state called formIsValid, initially set to false. My form also has a submit button. I want the submit button to be disabled until after some input fields (a first name and a last name input) have data in them.
This is what my state looks like:
state = {
employees: [],
costEstimates: emptyCosts(),
relationshipOptions: [],
newEmployee: emptyEmployee(),
formIsValid: false
};
This function handles changes to the First and Last name inputs:
// handle input into "First Name" and "Last Name" inputs
handleChangeValue = async e => {
const newEmployee = { ...this.state.newEmployee };
newEmployee[e.currentTarget.name] = e.currentTarget.value;
this.setState({ newEmployee });
this.validateIfCanBeSubmitted();
await this.updateCostsData(newEmployee); // this is an api thing, not relevent
};
This is what sets the formIsValid property in the state. This property is sent as a prop to the Submit button.
validateIfCanBeSubmitted = () => {
const { firstName, lastName } = this.state.newEmployee;
let formIsValid = firstName && lastName ? true : false;
this.setState({ formIsValid });
};
The Submit button for this is correctly getting disabled if the employee property in the state has its first and last names as empty. The problem is that it's "off by 1 update." It's as if the props aren't getting propagated down to the child button component until after the NEXT time the state changes. Here's a gif of the issue:
This is what the child component looks like. It's just a regular HTML button, however it's within a Stateless Functional Component, so the issue is not with the component's state:
<button
type="button"
onClick={onSubmit}
className={'btn btn-primary mr-1 ' + (formIsValid ? '' : 'disabled')}
disabled={!formIsValid}
>
setState() is asynchronous!
this.validateIfCanBeSubmitted(); is executed on the old state; this update this.setState({ newEmployee }); has not been propagated to this.state when your function is executed.
Make validateIfCanBeSubmitted an update-function.
validateIfCanBeSubmitted = ({ newEmployee: { firstName, lastName }}) => {
return {
formIsValid: firstName && lastName ? true : false
};
}
and use it accordingly:
handleChangeValue = async e => {
const {name, value} = e.currentTarget;
const newEmployee = {
...this.state.newEmployee,
[name]: value
};
this.setState({ newEmployee });
this.setState(this.validateIfCanBeSubmitted);
// this is an api thing, not relevant
await this.updateCostsData(newEmployee);
};
Actually, the code in handleChangeValue should also be in such a function, as it uses the previous state to compute the new one.
so how about combining them:
handleChangeValue = e => {
const {name, value} = e.currentTarget;
this.setState((state) => {
const newEmployee = {
...this.state.newEmployee,
[name]: value
};
const { firstName, lastName } = newEmployee;
const formIsValid = firstName && lastName ? true : false;
//and since you never use the returned Promise, why make anything async?
this.updateCostsData(newEmployee);
return { newEmployee, formIsValid };
});
};