setState not updating the state object - reactjs

I'm learning react from the book full stack react. In one of the examples votingapp where you have products and a button to vote for the product. That button supposes to increase the number of votes for that product and I store the votes number in a component Parent's state and display it in a child component. That voting feature is not working.
I created Parent component where it displays child component that present product description, id, color and votes (the number of votes the product received)
import React, { Component } from 'react';
import './App.css';
import Product from "./Product";
var products = [
{
id: 1,
name: "one",
color: "blue",
votes:0
},
{
id: 2,
name: "two",
color: "green",
votes : 0
},
{
id: 3,
name: "three",
color: "Red",
votes : 1
}
];
class App extends Component {
//this function will be passed to child component so the child can pass any data needed back to the parent using function argument.
handleProductUpVote(productId) {
console.log("Product #" + " " +productId + " was upvoted")
};
render() {
const productComponents = products.map((product) => {
return <Product
key={"product-" + product.id}
id={product.id}
name={product.name}
color={product.color}
votes={product.votes}
/*this is how we pass the function from parent to the child as a prop*/
onVote={this.handleProductUpVote}
/>
});
return (
<div className="App">
{productComponents}
</div>
);
}
}
export default App;
and here is my child component where it renders the product details
import React, { Component } from 'react';
class Product extends Component {
constructor(props) {
super(props);
this.handleUpVote = this.handleUpVote.bind(this);
}
//using the function passed from parent in a child. We pass any data needed back to parent component using parent's function arugments
// invoke this function using onClick event inside the button
handleUpVote() {
this.props.onVote(this.props.id);
};
render() {
return (
<div>
<p> name: {this.props.name} </p>
<p> MYcolor: {this.props.color} </p>
{/*invoke the function using onClick so it will invoke handleUpVote that will update the prop and pass the data back to parent*/}
<button onClick={this.handleUpVote}> Upvote Me </button>
<p>{this.props.votes}</p>
<hr></hr>
</div>
)
}
};
export default Product;
this is working and I log to the console the message when I hit the button "Upvoteme"
But When I'm trying to move the setup to use state. It doesn't work Here is the parent component with the state and setState. When I click on the vote button nothing happens to the vote count.
import React, { Component } from 'react';
import './App.css';
import Child from "./Child";
var productseed = [
{
id: 1,
name: "one",
color: "blue",
votes: 0
},
{
id: 2,
name: "two",
color: "green",
votes : 0
},
{
id: 3,
name: "three",
color: "Red",
votes : 1
}
];
class Parent extends Component {
constructor(props) {
super(props);
this.state = {
products: [],
};
this.handleProductUpVote = this.handleProductUpVote.bind(this);
}
componentDidMount() {
this.setState ({ products: productseed });
}
//this function will be passed to child component so the child can pass any data needed back to the parent using function argument.
handleProductUpVote(productId) {
// updating the vote in state
const nextProducts = this.state.products.map((product) => {
if (product.id === productId) {
return Object.assign({}, product, {
votes: product.votes + 1,
});
} else {
return product
}
});
this.setState({
products: nextProducts,
});
}
render() {
const productComponents = productseed.map((product) => {
return <Child
key={"product-" + product.id}
id={product.id}
name={product.name}
color={product.color}
votes={product.votes}
/*this is how we pass the function from parent to the child as a prop*/
onVote={this.handleProductUpVote}
/>
});
return (
<div className="App">
parent
{productComponents}
</div>
);
}
}
export default Parent;
The line below suppose to point to the products in the state but when I highlight it, it doesn't highlight the products in the this.state.
The consolelog is logging the message to my developer console. This is the issue, products in this.setState isn't pointing to the this.state.products and therefore not updating the state.
componentDidMount() {
this.setState({ products: productseed });
console.log("this products not pointing to the this.state.products, why?")
}
I read every question on stackoverflow related to setState not working and yet I have the same problem. If you an expert with react and able to take a look at this and figure out where is the issue, I would be so grateful. I'm unable to figure out when I assigned the this.setState({products: productseed}) and it doesn't update the state. I spent almost the past 4 hours reading and researching, please help.

Your problem lies in the render method of your Parent component. You're iterating over the productseeds array instead of your state. Since you're updating the state and not the seed array react sees no reason to rerender anything and therefore nothing changes.
So if you change that line from
const productComponents = productseed.map((product) => {...
to
const productComponents = this.state.products.map((product) => {...
you should be fine.
Moreover about your:
The line below suppose to point to the products in the state but when I highlight it, it doesn't highlight the products in the this.state.
This is just something related to the IDE you're using and nothing specific about react. You're passing an object with attributes and most IDEs (or all (?)) don't connect the combination with this.setState to the state object.

Related

passing array of objects from parent component to child component

this is a very simple react app with just App component and Child component. I am passing objects individually to the Child component using .map function in the parent. Each object is received in child(console), but I cannot get the object fields to output/display on browser or console, it just showing as a blank screen, also does not show any errors.
each person is recieved but individual fields cannot be from access like {person.Name} in the child component, I have also tried destructing the prop, still does not work
I have looked at other questions
Passing data from Parent Component to Child Component
How to pass Array of Objects from Parent Component to Child Component in react
before asking this question plus I have searched a lot online for solution
PARENT COMPONENT
import React from 'react'
import './App.css';
import Child from './Child'
function App() {
let persons = [
{
Name: 'Bill Gates',
id: 432,
Place: 'USA'
},
{
Name: 'Danny Archer',
id: 34,
Place: 'Blood Diamond'
},
{
Name: 'Iphone X',
id: 2018,
Place: 'USA'
},
{
Name: 'React App',
id: 111,
Place: 'Front End Programming'
},
{
Name: 'Honda CBR 600RR',
id: 100,
Place: 'Bike'
},
]
console.log(persons);
return (
<div className="App">
{
persons.map(person=>(<Child person = {person} key={person.id}/>))
}
</div>
);
}
export default App;
CHILD COMPONENT
import React from 'react'
function Child(person) {
// console.log(person)
return (
<div >
<h1>{person.Name}</h1>
</div>
)
}
export default Child
Props is an object, not the exact value you were expecting
import React from 'react'
/// change this line
function Child({person}) {
// console.log(person)
return (
<div >
<h1>{person.Name}</h1>
</div>
)
}
export default Child
In your code, function Child(person)
this person, is an object containing the props you pass like, for example Child(person={person} someData={someData})
so, person param in function will be
{
person: person,
someData: someData
}
If you console.log person in child component you will get:
person: { person: { name, id, place }}
because the first parameter will get all of the properties from the parent.
So instead of doing this --> function Child(person), you can do something like this --> function Child({person}). So this will destructure your prop object & you will get your person object!

How do I limit the user to only selecting one component?

I have the following code that simply constructs blocks for our products and the selected state allows the component to be selected and unselected. How can I figure out which of these components are selected and limit the user to only selecting one at a time. This is ReactJS code
import React from 'react';
export default class singleTile extends React.Component{
constructor(props){
super(props);
this.title = this.props.title;
this.desc = this.props.desc;
this.svg = this.props.svg;
this.id = this.props.id;
this.state = {
selected: false
}
}
selectIndustry = (event) => {
console.log(event.currentTarget.id);
if(this.state.selected === false){
this.setState({
selected:true
})
}
else{
this.setState({
selected:false
})
}
}
render(){
return(
<div id={this.id} onClick={this.selectIndustry}className={this.state.selected ? 'activated': ''}>
<div className="icon-container" >
<div>
{/*?xml version="1.0" encoding="UTF-8"?*/}
{ this.props.svg }
</div>
</div>
<div className="text-container">
<h2>{this.title}</h2>
<span>{this.desc}</span>
</div>
</div>
)
}
}
You need to manage the state of the SingleTile components in the parent component. What i would do is pass two props to the SingleTile components. A onClick prop which accepts a function and a isSelected prop that accepts a boolean. Your parent component would look something like this.
IndustrySelector.js
import React from 'react';
const tileData = [{ id: 1, title: 'foo' }, { id: 2, title: 'bar' }];
class IndustrySelector extends Component {
constructor(props) {
super(props);
this.state = { selectedIndustry: null };
}
selectIndustry(id) {
this.setState({ selectedIndustry: id });
}
isIndustrySelected(id) {
return id === this.state.selectedIndustry;
}
render() {
return (
<div>
{tileData.map((data, key) => (
<SingleTile
key={key}
{...data}
onClick={() => this.selectIndustry(data.id)}
isSelected={this.isIndustrySelected(data.id)}
/>
))}
</div>
);
}
}
The way this works is as follows.
1. Triggering the onClick handler
When a user clicks on an element in SingleTile which triggers the function from the onClick prop, this.selectIndustry in the parent component will be called with the id from the SingleTile component.
Please note that in this example, the id is remembered through a
closure. You could also pass the id as an argument to the function of
the onClick prop.
2. Setting the state in the parent component
When this.selectIndustry is called it changes the selectedIndustry key of the parent component state.
3. Updating the isSelected values form the SIngleTile components
React will automatically re-render the SingleTile components when the state of the parent component changes. By calling this.isIndustrySelected with the id of the SingleTile component, we compare the id with the id that we have stored in the state. This will thus only be equal for the SingleTile that has been clicked for the last time.
Can you post your parent component code?
It's not so important, but you can save some time by using this ES6 feature:
constructor(props){
super(props);
const {title, desc, svg, id, state} = this.props;
this.state = {
selected: false
}
}

How to initialize the state of each item component in the component array in React?

const products = [
{
id: 1,
...
},
{
id: 2,
...
},
{
id: 3,
...
},
];
I created the ProductList component, which contains 3 Product components:
class ProductList extends Component {
constructor(props) {
super(props)
}
render() {
const productComponents = products.map((product) => (
<Product
key = {'product-' + product.id}
id = {product.id}
...
/>
));
return (
<ul className="holder-list row">
{productComponents}
</ul>
);
}
}
class Product extends Component {
constructor(props) {
super(props)
}
render() {
return(..)
}
}
How and in which component in the constructor to set a different initial state for all three products?
I want set the initial value of this.state for each Product different.
Example:
for Product with id:1 - this.state={color: blue},
for Product with id:2 - this.state={color: yellow},
for Product with id:3 - this.state={color: red}.
How can I do something like this?
This is how you could approach setting state color for Product. These are both inspired from a great article You Probably Don't Need Derived State which provides some great examples on how to handle "derived state".
ProductList - Create a method the returns a string color value based on your id to color requirements. This can be outside of the class definition, it doesn't/shouldn't need to be a method on class ProductList as it doesn't need the this or similar. Add an additional prop, something like defaultColor, that is passed to each instance of Product:
const getColor = id => {
switch (id) {
case 1:
return 'blue';
case 2:
return 'yellow';
case 3:
return 'red'
default:
return '';
}
};
// ...
render() {
const productComponents = products.map((product) => (
<Product
key = {'product-' + product.id}
id = {product.id}
defaultColor={getColor(product.id)}
...
/>
));
}
Product - Set initial state using the defaultColor prop being passed in. Using a different property would allow each Product component to fully control it's own color state value/changes with something like an <input />, but copy over the initial color value:
class Product extends Component {
state = { color: this.props.defaultColor };
// ...
render() {
return ({/* ... */});
}
}
Here is a StackBlitz demonstrating the functionality in action.
The other options is using static getDerivedStateFromProps() in Product. It conditionally checks if the id prop has changed to avoid setting state unnecessarily and overriding Product local state values. We are keeping track of the previous id value so that it can be used in the conditional statement to see if any changes actually happened:
class Product extends Component {
constructor(props) {
super(props);
this.state = {
prevId: -1,
color: ''
};
}
static getDerivedStateFromProps(props, state) {
if (props.id !== state.prevId) {
switch (props.id) {
case 1:
return { color: 'blue', prevId: props.id };
case 2:
return { color: 'yellow', prevId: props.id };
case 3:
return { color: 'red', prevId: props.id };
default:
return null;
}
}
return null
}
render() {
return ({/* ... */});
}
}
Here is a StackBlitz demonstrating this functionality.
It's hard to say exactly how to approach this as it may be likely you do not need state in Product. That Product can act as a "dumb" component just receiving props and emitting value changes to a higher order component like ProductList.
Hopefully that helps!

How to pass props to recursive child component and preserve all props

I've run into an interesting problem. I have a parent component that has an array of objects that gets passed to a child component that is a TreeView, meaning it is recursive. I'm passing a function, and a couple of other props to the child, along with the array of objects that is handled recursively by the child. When logging the props in the render function of the child, on the first render all the props are there, but as the recursive function moves through each object in the array, it 'loses' all the other props that are not being handled recursively.
When the component first renders the props object is: prop1, prop2, arrayOfObjects
As it re-renders as recursion is happening, the props object in the child becomes: arrayOfObjects.
prop1, and prop2 have disappeared.
The end result is that I'm not able to call a function in the parent from the child, so I cannot update the state depending on which node in the tree is clicked. I'm not using redux, because this is a style guide - separate from our production app, that is meant to be for devs only, and simple so if possible I'd like to handle all the state from within the components.
There is one other issue - The array of objects is the folder structure of files in our styleguide, and I need to be able to click on a name in the list, and update the view with the contents of that file. This works fine when the file does not have any children, but when there are child nodes, if I click on the parent, the child is clicked. I've tried e.stopPropagation(), e.preventDefault() etc. but have not had any luck. Thanks in advance.
Parent:
import React, {Component} from 'react'
import StyleGuideStructure from '../../styleguide_structure.json'
import StyleTree from './style_tree'
class StyleGuide extends Component {
constructor(props) {
super(props)
let tree = StyleGuideStructure
this.state = {
tree: tree
}
This is the function I'd like to call from the child
setVisibleSection(nodeTitle) {
this.setState({
section: nodeTitle
})
}
render() {
return(
<TreeNode
className="class-name-here"
setVisibleSection={this.setVisibleSection.bind(this)}
node={this.state.tree}
/>
)
}
}
export default StyleGuide
This is essentially what I have in the child, as a fiddle here:
https://jsfiddle.net/ssorallen/XX8mw/
The only difference is that inside the toggle function, I'm trying to call setVisibleSection in the parent, but no dice.
Here is a photo of the console showing the props when the component initially renders, and then after recursion:
I don't think I really understand your 2nd issue. Could you post a fiddle showing the problem?
I think your first issue is that you need to pass the props down to the children. I tried to transcribe your example to your fiddle. You can see by clicking the nodes, the title switched to the node's name.
https://jsfiddle.net/hbjjq3zj/
/**
* Using React 15.3.0
*
* - 2016-08-12: Update to React 15.3.0, class syntax
* - 2016-02-16: Update to React 0.14.7, ReactDOM, Babel
* - 2015-04-28: Update to React 0.13.6
*/
class TreeNode extends React.Component {
constructor(props) {
super(props);
this.state = {
visible: true,
};
}
toggle = () => {
this.setState({visible: !this.state.visible});
this.props.setVisibleSection(this.props.node.title)
};
render() {
var childNodes;
var classObj;
if (this.props.node.childNodes != null) {
childNodes = this.props.node.childNodes.map((node, index) => {
return <li key={index}><TreeNode {...this.props} node={node} /></li>
});
classObj = {
togglable: true,
"togglable-down": this.state.visible,
"togglable-up": !this.state.visible
};
}
var style;
if (!this.state.visible) {
style = {display: "none"};
}
return (
<div>
<h5 onClick={this.toggle} className={classNames(classObj)}>
{this.props.node.title}
</h5>
<ul style={style}>
{childNodes}
</ul>
</div>
);
}
}
class ParentComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
visible: true,
};
}
toggle = () => {
this.setState({visible: !this.state.visible});
};
setVisibleSection(nodeTitle) {
this.setState({
title: nodeTitle
})
}
render() {
return (
<div>
Title: {this.state.title}
<TreeNode
node={tree}
setVisibleSection={this.setVisibleSection.bind(this)}
/>
</div>
);
}
}
var tree = {
title: "howdy",
childNodes: [
{title: "bobby"},
{title: "suzie", childNodes: [
{title: "puppy", childNodes: [
{title: "dog house"}
]},
{title: "cherry tree"}
]}
]
};
ReactDOM.render(
<ParentComponent />,
document.getElementById("tree")
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>

Rendering Firebase list on React

I followed this https://www.youtube.com/watch?v=p4XTMvagQ2Q introductory tutorial on Firebase and React. I am trying to take this further but I have been stuck for a while. Basically, I want to retrieve a list of child from a firebase node and render it as list/table. Currently, only the last child is being rendered since every other one is being overwritten.
import React, { Component } from 'react';
import './App.css';
class App extends Component {
constructor(){
super();
this.state = {
id : 0,
latitude : 0,
longitude : 0
};
}
componentDidMount(){
const rootRef = firebase.database().ref().child('drivers');
rootRef.on('child_added', snap => {
console.log(snap.key);
this.setState({
id : snap.key,
latitude : snap.val().lat,
longitude : snap.val().lon
});
});
}
render() {
return (
<div className="App">
<h1>{this.state.id}</h1>
<h1>{this.state.latitude}</h1>
<h1>{this.state.longitude}</h1>
</div>
);
}
}
export default App;
How do I update the render function to display the whole list?
What is happening is you're retrieving the whole list, but for every element in the list, you're overriding your state with its value. Then, in your state, you'll always have the last values from the list.
What you can do in this case is store a list of elements, let's say positions:
this.state = {
listOfPositions: []
};
Then when a child is added, you append the contents of the new child to your current list:
rootRef.on('child_added', snap => {
const previousList = this.state.listOfPositions;
previousList.append({
id: snap.key,
latitude: snap.val().lat,
longitude: snap.val().lon
});
this.setState({
listOfPositions: previousList;
});
});
Finally, in your render method, you generate a list of react elements for every element in your listOfPositions and render it in the render method:
render() {
const listOfPositions = this.state.listOfPositions.map(position =>
<div>
<h1>{position.id}</h1>
<h1>{position.latitude}</h1>
<h1>{position.longitude}</h1>
</div>
);
return (
<div>{listOfPositions}</div>
);
}
Note that when you're setting your reference to the database and you get your value from this.state.listOfPositions, you can't guarantee that the state value is the most updated one. So if you want to be sure about it use the other definition for the this.setState() method like this:
rootRef.on('child_added', snap => {
const newPosition = {
id: snap.key,
latitude: snap.val().lat,
longitude: snap.val().lon
};
this.setState(previousState => {
listOfPositions: previousState.listOfPositions.append(newPosition);
});
});
This will make sure when you call this.setState() you will have the last value of state in your previousState variable.

Resources