reactJs update state object without overwriting the object - reactjs

I am having problems updating an object in this.state. Basically I have a form with multiple range sliders. Each time the slider is changed I want to update the this.state.dreadModel object, however I only seem to be able to overwrite it.
This is what I have tried so far:
this.setState( update ( this.state.dreadModel, { dreadModel: { [name]: e.target.value } } ), function () {
console.log(this.state);
});
The error I get with the above code is "Uncaught ReferenceError: update is not defined".
I feel like I am very close, but I cant quite get the syntax quite right.
Oh, here is the code I was using that would just overwrite the object each time a sliders value was changed.
this.setState({ dreadModel: {[name]: e.target.value }}, function () {
console.log(this.state);
});

What I usually do if I need to update a nested object, is something more like:
var dreadModel = {...this.state.dreadModel, [name]: e.target.value };
//or, if no ES6:
//var dreadModel = Object.assign({}, this.state.dreadModel);
//dreadModel[name] = e.target.value;
this.setState({ dreadModel: dreadModel }, function () {
console.log(this.state);
});

Related

Render an array as list with onClick buttons

I'm new at ReactJs development, and I'm trying to render a list below the buttons I created with mapping my BE of graphQl query. I don't know what I'm doing wrong (the code has a lot of testing on it that I tried to solve the issue, but no success.)
The buttons rendered at getCategories() need to do the render below them using their ID as filter, which I use another function to filter buildFilteredCategoryProducts(categoryParam).
I tried to look on some others questions to solve this but no success. Code below, if need some more info, please let me know!
FYK: I need to do using Class component.
import React, { Fragment } from "react";
import { getProductsId } from "../services/product";
import { getCategoriesList } from "../services/categories";
//import styled from "styled-components";
class ProductListing extends React.Component {
constructor(props) {
super(props);
this.state = {
category: { data: { categories: [] } },
product: { data: { categories: [] } },
filteredProduct: { data: { categories: [] } },
};
this.handleEvent = this.handleEvent.bind(this);
}
async handleEvent(event) {
var prodArr = [];
const testName = event.target.id;
const testTwo = this.buildFilteredCategoryProducts(testName);
await this.setState({ filteredProduct: { data: testTwo } });
this.state.filteredProduct.data.map((item) => {
prodArr.push(item.key);
});
console.log(prodArr);
return prodArr;
}
async componentDidMount() {
const categoriesResponse = await getCategoriesList();
const productsResponse = await getProductsId();
this.setState({ category: { data: categoriesResponse } });
this.setState({ product: { data: productsResponse } });
}
getCategories() {
return this.state.category.data.categories.map((element) => {
const elName = element.name;
return (
<button id={elName} key={elName} onClick={this.handleEvent}>
{elName.toUpperCase()}
</button>
);
});
}
buildFilteredCategoryProducts(categoryParam) {
const filteredCategories = this.state.product.data.categories.filter(
(fil) => fil.name === categoryParam
);
let categoryProducts = [];
filteredCategories.forEach((category) => {
category.products.forEach((product) => {
const categoryProduct = (
<div key={product.id}>{`${category.name} ${product.id}`}</div>
);
categoryProducts.push(categoryProduct);
});
});
return categoryProducts;
}
buildCategoryProducts() {
const filteredCategories = this.state.product.data.categories;
let categoryProducts = [];
filteredCategories.forEach((category) => {
category.products.forEach((product) => {
const categoryProduct = (
<div key={product.id}>{`${category.name} ${product.id}`}</div>
);
categoryProducts.push(categoryProduct);
});
});
return categoryProducts;
}
buildProductArr() {
for (let i = 0; i <= this.state.filteredProduct.data.length; i++) {
return this.state.filteredProduct.data[i];
}
}
render() {
return (
<Fragment>
<div>{this.getCategories()}</div>
<div>{this.buildProductArr()}</div>
</Fragment>
);
}
}
export default ProductListing;
Ok, so this won't necessarily directly solve your problem,
but I will give you some pointers that would definitely improve some of your code and hopefully will strengthen your knowledge regarding how state works in React.
So first of all, I see that you tried to use await before a certain setState.
I understand the confusion, as setting the state in React works like an async function, but it operates differently and using await won't really do anything here.
So basically, what we want to do in-order to act upon a change of a certain piece of state, is to use the componentDidUpdate function, which automatically runs every time the component re-renders (i.e. - whenever there is a change in the value of the state or props of the component).
Note: this is different for function components, but that's a different topic.
It should look like this:
componentDidUpdate() {
// Whatever we want to happen when the component re-renders.
}
Secondly, and this is implied from the previous point.
Since setState acts like an async function, doing setState and console.log(this.state) right after it, will likely print the value of the previous state snapshot, as the state actually hasn't finished setting by the time the console.log runs.
Next up, and this is an important one.
Whenever you set the state, you should spread the current state value into it.
Becuase what you're doing right now, is overwriting the value of the state everytime you set it.
Example:
this.setState({
...this.state, // adds the entire current value of the state.
filteredProduct: { // changes only filteredProduct.
...filteredProduct, // adds the current value of filteredProduct.
data: testTwo
},
});
Now obviously if filteredProduct doesn't contain any more keys besides data then you don't really have to spread it, as the result would be the same.
But IMO it's a good practice to spread it anyway, in-case you add more keys to that object structure at some point, because then you would have to refactor your entire code and fix it accordingly.
Final tip, and this one is purely aesthetic becuase React implements a technique called "batching", in-which it tries to combine multiple setState calls into one.
But still, instead of this:
this.setState({ category: { data: categoriesResponse } });
this.setState({ product: { data: productsResponse } });
You can do this:
this.setState({
...this.state,
category: {
...this.state.category,
data: categoriesResponse,
}
product: {
...this.state.product,
data: productsResponse,
},
})
Edit:
Forgot to mention two important things.
The first is that componentDidUpdate actually has built-in params, which could be useful in many cases.
The params are prevProps (props before re-render) and prevState (state before re-render).
Can be used like so:
componentDidUpdate(prevProps, prevState) {
if (prevState.text !== this.state.text) {
// Write logic here.
}
}
Secondly, you don't actually have to use componentDidUpdate in cases like these, because setState actually accepts a second param that is a callback that runs specifically after the state finished updating.
Example:
this.setState({
...this.state,
filteredProduct: {
...this.state.filteredProduct,
data: testTwo
}
}, () => {
// Whatever we want to do after this setState has finished.
});

Set state of react component before mount with firebase data

I am trying to setState of my component before it mounts (in the cmoponentWillMount) with data I pull from Firebase but I am getting an error saying "Cannot read property 'setState' of undefined. How do I properly set the state so that my component loads with the proper data?
componentWillMount(){
const rootRef = fire.database().ref('groups')
rootRef.limitToFirst(1).once('value', function(snap) {
snap.forEach(function(child){
var first = (child.val()).id;
console.log(first);
this.setState({ selectedGroupId: first });
})
});
}
Try this.
Never do setState in loop and you get that error because you are using regular function so change it to arrow function like below. Also switch to componentDidMount method because componentWillMount is deprecated
componentDidMount(){
const rootRef = fire.database().ref('groups')
rootRef.limitToFirst(1).once('value', snap => {
let first = 0;
snap.forEach(child => {
first = (child.val()).id;
console.log(first);
})
this.setState({ selectedGroupId: first });
});
}
Or bind it like this if you don’t like to use an arrow function
componentDidMount(){
const rootRef = fire.database().ref('groups')
rootRef.limitToFirst(1).once('value', function(snap) {
let first = 0;
snap.forEach(function(child){
first = (child.val()).id;
console.log(first);
}.bind(this));
this.setState({ selectedGroupId: first });
}.bind(this));
}
Start using let & const instead of var.
It's because the scope for this is undefined. You need to pass it through with ES6 arrow functions.
componentWillMount(){
const rootRef = fire.database().ref('groups')
rootRef.limitToFirst(1).once('value', (snap) => {
snap.forEach((child) => {
var first = (child.val()).id;
console.log(first);
this.setState({ selectedGroupId: first });
})
});
}
Try it this way.

Reacjs/Graphql: Pass variables values to query from event

So the code below is updating the state of inputValue but for some reason that value is not be passed to the query as the following error is shown:
[GraphQL error]: Message: Variable "$timestamp" of required type "Float!" was not provided., Location: [object Object], Path: undefined
So my question is how do I assign the inputValue to timestamp and pass timestamp to the getObjectsQuery?
class Calendar extends React.Component {
constructor(props) {
super(props);
this.state = {
inputValue: ""
};
this.handleSubmit = this.handleSubmit.bind(this);
}
handleSubmit = event => {
event.preventDefault();
console.log(this.state.inputValue);
this.setState({
inputValue: new Date(document.getElementById("time").value).valueOf()
}); //Parent component contains submit button and there lives state. Submit handler should only set value in state with...setState()- NOT directly
this.props.data.refetch({
//For some reason
timestamp: this.state.inputvalue
});
console.log(this.state.inputValue);
};
render() {
console.log(this.props);
return (
<div className="Calendar">
<form onSubmit={this.handleSubmit.bind(this)}>
<label>Date/Time</label>
<input type="datetime-local" id="time" step="1" />
<input type="submit" value="Submit" />
</form>
</div>
//{this.render(){return (<UserList />)};
);
}
}
export default graphql(getObjectsQuery, {
options: props => ({
variables: {
timestamp: props.inputvalue
}
})
})(Calendar);
I know it's already solved in another place Reactjs/Graphql: TypeError: Object(...) is not a function
Just to remember (as you stil not learned):
handleSubmit = event => {
event.preventDefault();
console.log(this.state.inputValue); // OLD VALUE
this.setState({
inputValue: new Date(document.getElementById("time").value).valueOf()
});
this.props.data.refetch({
//For some reason
timestamp: this.state.inputvalue
// THERE IS STILL OLD VALUE
// because setState work asynchronously
// IT WILL BE UPDATED LATER
});
console.log(this.state.inputValue); // STILL OLD VALUE
};
To use value from event you could simply use its value, not passing it through 'async buffer' (state).
handleSubmit = event => {
event.preventDefault();
console.log(this.state.inputValue); // OLD VALUE
const timestamp = new Date(document.getElementById("time").value).valueOf()
console.log(timestamp); // NEW VALUE
// use new value directly
this.props.data.refetch({
timestamp: +timestamp
// convert to int
});
// save in state - IF NEEDED at all
this.setState({
inputValue: timestamp
});
};
Of course using setState callback is a quite good workaround, too.
Keep in ming that you can have 2 renders - one when state changes and second when data arrives. If storing value in state isn't really required you can avoid one unnecessary rendering.

React Binding and Populating an Object

I have a React Component which maintains a Local State as shown below:
this.state = {
user : {}
}
I have 2 textboxes on the screen where I can enter name and age. I am sending the values from the textboxes and populating the user object inside the state as shown below:
onTextBoxChange = (e) => {
// this.user is a property on the Component declared inside the // constructor
this.user[e.target.name] = e.target.value
this.setState({
user : this.user
})
}
Is there a better way of populating the state with the user object which consists of name and age.
There is no need to create an extra variable (let user =...) or a member variable (this.user).
Just overwrite previous values using spread syntax.
onTextBoxChange = e => {
this.setState({
user: {
...this.state.user,
[e.target.name]: e.target.value
}
});
};
Here is the fully working version on CodeSandBox.
You can achieve the same by following three different ways:
1) There's no need to creating an this.user property, instead to can achieve the same by using a local variable. So, the function can be changed to:
onTextBoxChange = (e) => {
let user = this.state.user;
user[e.target.name] = e.target.value;
this.setState({
user: this.user
})
}
2) If you're not using user state in input text boxes for value, then you can simply use this.user property instead of using a state, which will cause re-rendering.
You can then use the this.user when the form submitted, for example.
3) Again, If you're not using user state in input text boxes for value, then you can also use React refs and get the value directly from the element when the form is submitted, for example.
You might try this in a parent component:
onTextBoxChange = (field, value) => {
this.setState({user:{
...this.state.user,
[field]: value
}
});
}
and then this in the child component:
onNameChange = (e) => {
this.props.onChange('name', e.target.value)
}
onAgeChange = (e) => {
this.props.onChange('age', e.target.value)
}

Unable to save input picture data in state variable React.js

i'm trying to learn how the image upload process works with react. i have an input that takes image data in and a handler that sets that information to a variable but i am doing something incorrect and not sure what it is.
I want to update picture in state with the picture information and leave the other items in state alone. When i do the below, and console.log after the state of picture stays null.
class Profile extends Component {
constructor() {
super();
this.state = {
userName: "",
userEmail: "",
picture: null
};
this.handleNewImage = this.handleNewImage.bind(this);
}
handleNewImage = event => {
this.setState({
picture: event.target.files[0]
})
console.log(this.state.picture); //gives null still
}
render() {
return (
<input type='file' onChange={this.handleNewImage} />
);
}
}
This probably has nothing to do with images but rather an understanding of setState. React's setState is an async operation so you'd need to console log once the state update is complete:
handleNewImage = event => {
this.setState({
picture: event.target.files[0]
}, () => {
console.log(this.state.picture);
});
}
More info here:
https://reactjs.org/docs/react-component.html#setstate
https://css-tricks.com/understanding-react-setstate/

Resources