My state looks like this
state = {
basic: {
entry: 'index.js',
output: {
path: 'dist',
filename: 'bundle.js',
}
}
}
I have defined a callback for input onChange event :
handleUpdateString = (e) => {
const name = e.target.name
const value = e.target.value
this.setState({ [name]: value })
console.log(this.state)
}
say my input name is 'basic.entry'
my state is updated, but instead of having this.state.basic be { entry: 'some value'}
I end up with a new key in my state : { basic.entry: 'some value' }
I have tried using dot-object to convert the dot-seperated string to a nested object, and passing this to setState but the state appears to be unchanged.
What are simple solutions to this problem ?
Use a switch statement with explicit setState.
This is not a React question but a pure JavaScript question on how to set a nested property on any object using a dot-notated string for the property. There is an answer here: Javascript object key value coding. Dynamically setting a nested value
Using that example in React:
handleUpdateString = (e) => {
const name = e.target.name
const value = e.target.value
const state = Object.assign({}, this.state);
setData(name, value, state);
this.setState(state);
console.log(this.state)
}
Related
I have an array of default values in a useState. Upon an onChange event via a select, the properties in the array are changed to undefined. I've been reading up on controlled components and I think I've missed something silly.
Here is a codesandbox.
const [config, setConfig] = useState([{
carType: "Sedan",
carColor: "Red"
}]);
// onChange
const setCarType = (e) => {
setConfig({ ...config[0], carType: e.target.value });
};
const { carType, carColor } = config[0];
I thought that I could use the spread operator here in order to copy the two properties of config[0] into two separate constants. I originally had the config in an object but was thrown an error "Objects are not valid as a react child".
const setCarType = (e) => {
setConfig([{ ...config[0], carType: e.target.value }]);
};
const setCarColor = (e) => {
setConfig([{ ...config[0], carColor: e.target.value }]);
};
When you were setting config, you were setting a new object as config but it was initialized as an array of objects. So config[0] was undefined cause config was no more an array. Try the above code, it will solve the issue.
I want to update value of one object only but updating value of one Object, Updates the value for all objects.
let default = {
name: '',
age: ''
}
this.state = {
values: Array(2).fill(default)
}
updateName (event) {
let index = event.target.id,
values = this.state.values;
values[index].name = event.target.value;
this.setState ({
values: values
});
}
There are four significant problems in that code.
You're using the same object for all entries in your array. If you want to have different objects, you have to create multiple copies of the default.
You're calling setState incorrectly. Any time you're setting state based on existing state (and you're setting values based, indirectly, on this.state.values), you must use the function callback version of setState. More: State Updates May Be Asynchronous
You can't directly modify the object held in this.state.values; instead, you must make a copy of the object and modify that. More: Do Not Modify State Directly
default is a keyword, you can't use it as an identifier. Let's use defaultValue instead.
Here's one way you can address all four (see comments):
// #4 - `default` is a keyword
let defaultValue = {
name: '',
age: ''
};
this.state = {
// #1 - copy default, don't use it directly
values: [
Object.assign({}, defaultValue),
Object.assign({}, defaultValue),
] // <=== Side note - no ; here!
};
// ....
updateName(event) {
// Grab the name for later use
const name = event.target.value;
// Grab the index -- I __don't__ recommend using indexed updates like this;
// instead, use an object property you can search for in the array in case
// the order changes (but I haven't done that in this code).
const index = event.target.id;
// #2 - state updates working from current state MUST use
// the function callback version of setState
this.setState(prevState => {
// #3 - don't modify state directly - copy the array...
const values = prevState.values.slice();
// ...and the object, doing the update; again, I wouldn't use an index from
// the `id` property here, I'd find it in the `values` array wherever it
// is _now_ instead (it may have moved).
values[index] = {...values[index], name};
return {values};
});
}
Note that this line in the above:
values[index] = {...values[index], name};
...uses property spread syntax added in ES2018 (and shorthand property syntax, just name instead of name: name).
I would use the Array.prototype.map function with combination of the object spread syntax (stage 4):
Note that i changed the name of the default object to obj.
default is a reserved key word in javascript
let obj = {
name: '',
age: ''
}
this.state = {
values: Array(2).fill(obj)
}
updateName(event){
const {id, value} = event.target;
this.setState(prev => {
const {values} = prev;
const nextState = values.map((o,idx) => {
if(idx !== id)
return o; // not our object, return as is
return{
...o,
name: value;
}
});
return{
values: nextState
}
});
}
There is an easy and safe way to achieve that through the following:
this.setState({
values: [ newObject, ...this.state.values],
});
this will create an instance of the state and change the value of an existing object with new object.
Let's say a component has state such as:
this.state = {
enabled: {
one: false,
two: false,
three: false
}
}
How can this.setState() be used to set the value of a dynamic property?
For instance, this does not work:
let dynamicProperty = "one"
this.setState({
enabled[dynamicProperty]: true
})
However, this does work, but is also bad practice:
this.enabled = {
one: false,
two: false,
three: false
}
let dynamicProperty = "one"
this.enabled[dynamicProperty] = true;
How can this.setState() be used to accomplish the same thing?
You need to create a copy of the original object and only change the property you want to update. The easiest way to do that is to use the object spread operator:
this.setState(currentState => ({enabled: {...currentState.enabled, one: true}}));
or in a more verbose form:
this.setState(currentState => {
const enabled = {...currentState.enabled, one: true};
return {enabled};
});
If the property name is only known at runtime you can do it like this:
const setEnabled = name => {
this.setState(currentState => ({enabled: {...currentState.enabled, [name]: true}}));
};
The standard practice is to copy the the state, modify the copied state, then set state using that clone, like this:
//with spread operator
const enabledClone = {...this.state.enabled};
enabledClone.one = true;
this.setState({enabled : enabledClone});
You can use braces around an object's key to use a variable to determine the key
const dynamicKey = 'one';
const newObj = {[dynamicKey]: true} //equals {one: true}
Since this.setState only merges on toplevel keys, you will have to create a copy of the current enabled object and use the braces notation:
let dynamicProperty = "one"
this.setState({
enabled: {...this.state.enabled, [dynamicProperty]: true}
})
This question already has answers here:
Cannot access correct this inside an axios callback [duplicate]
(1 answer)
React this.setState is not a function
(16 answers)
Closed 4 years ago.
I'm trying to map over an array of objects and use the name key to pass it to the options prop in react-select. Can I just do this using regular JS? I'm trying to incorporate this example.
My mock data
mock.onGet("/dataschemas").reply(200, {
data: [
{
id: "2147483599",
selfUri: "/dataschemas/2147483599",
name: "Book Catalog"
},
{
id: "2147483600",
selfUri: "/dataschemas/2147483600",
name: "Business Articles"
},
{
id: "2147483602",
selfUri: "/dataschemas/2147483602",
name: "Phone Data"
}
]
});
In cDM I'm updating state with the response and storing it in schemas
componentDidMount() {
axios.get("/dataschemas").then(function(response) {
console.log(response.data.data);
this.setState({
schemas: response.data.data
});
console.log(this.state.schemas);
});
}
Then in my select component I'm setting the schemas to the options prop and mapping over that in the values prop
<Select
id="color"
options={this.state.schemas}
isMulti={false}
value={this.state.schemas.filter(
({ name }) => name === this.state.name
)}
getOptionLabel={({ name }) => name}
getOptionValue={({ id }) => id}
onChange={({ value }) => this.setState({ name: value })}
onBlur={this.handleBlur}
/>
I can't seem to get the right values in props, to display the dataschema names in the dropdown selection in my codesandbox example
More info on react-select docs pertaining to this issue
The <Select> component's value prop expect a single object/value. However in the following code:
this.state.schemas.filter(
({ name }) => name === this.state.name
)
Calling .filter on an array returns another array. So you're passing an array to value, not a single object. You just need to add a [0] to unwrap the array:
this.state.schemas.filter(
({ name }) => name === this.state.name
)[0]
Or use .find instead:
this.state.schemas.find(
({ name }) => name === this.state.name
)
here's a working codesandbox
it seems like the problem was that you were using function syntax instead of fat arrow syntax and as a result this was being bound incorrectly so this.setState was undefined
more info about the difference between function and fat arrow syntax here
How does this work? I like to think of it as fat arrow functions don’t
have their own or don’t change the context of ‘this’. They leave it
alone so that it stays the same as the context in which the function
was created.
For debugging future issues like this, I recommend starting off by looking at the javascript console errors -- the initial tip was an error there that this.setState was undefined
You are trying to access state from inside the response function scope.
change your componentDidMount to this:
async componentDidMount() {
const response = await axios.get("/dataschemas");
if (response.data && response.data.data) {
this.setState({ schemas: response.data.data });
}
}
I'm trying to set a state that I'm getting from an input target:
Below is the code for my constructor and the method which updates the target's state
constructor(props) {
super(props);
this.state = {
title: ''
};
this.onChange = this.onChange.bind(this);
}
onChange(e) {
this.setState({
[e.target.name] : e.target.value
});
}
Where the input is given by this code:
<input type={"text"} name={"title"} value={this.state.title} onChange={this.onChange}/>
When I debug my code I found that e.target.name contains "title" and I do not understanding why I need the squared brackets [] in [e.target.name] ... I found some explanation on the web but I have not understood what it means:
We use attr in this.setState with square brackets [ ] because [ ] lets
us query object key names programmatically (similar to how array[2] or
object[keyA] works)
Can you help me to understand why we need these brackets ?
FYI : If I remove the brackets as this :
onChange(e) {
this.setState({
e.target.name : e.target.value
});
}
this give me this error : "Unexpected token, expected" at e.target.name
This syntax is just another way to set a key of an object without knowing ahead of time what you want it to be called -- a computed property name.
For instance, these two examples accomplish the same thing:
const myNewObject = {
name: 'Joe',
age: 30
}
…
const propName = 'age'
const myNewObject = {
name: 'Joe',
[propName]: 30
}
So in your example– e.target.name comes from the name attribute of the input element ("title", in this case). By setting the state's key with [e.target.name] you're really just setting the title property of the state.
{ sth: else }
is equal to
{ ["sth"]: else }
or
var id = "sth";
{ [id]: else }
So you basically need it when you want to evaluate the identifier instead of taking it.