props is undefined although state has changed - javascript

I am new in react. I have a parent component Navbar, that has state "Total_Item". This array is added on click of Modal. The values are populating. Now i want to get the length of this array and show on my cart button(presently a simple button), in my Navv component. But it says undefined.
So the data is not saved in props (Tot_Item ) in the Navbar component. I am sure there is some conceptual error how react renders. A clear explanation will really help at this point.
Please see the sandbox below:
https://codesandbox.io/s/blazing-sky-cet22
Thanks
sal

In file Navbar.jsx, value of this.state.Tot_Item is empty array. Use this.setState function to change the value of this.state.Tot_Item
=> In file Navv.jsx value of this.props.Tot_Item is empty array. Change the way to render an array of button.
https://codesandbox.io/s/stoic-rubin-wg2fo

You don't need to use async and await in React, as it's supposed to work asynchronously. You can, however, pass a callback function to setState to do what you want. That method shall be passed as a second parameter in your setState call, and the function you pass will not be run until React has successfully updated the state.
this.setState({
myVar: 'value to print'
}, () => console.log(this.state.myVar));
Also, I've noticed you're calling setState a lot of times on your listval method. You actually don't have to call setState multiple times if you want to set many properties at once. Since the state is an object, you can change all of the properties you want in a single call, like this:
this.setState({
Select_Price: ll,
Select_Item: ll3,
Select_Item_TotalPrice: ll6,
Total_Item: ll7
});
As for why this.props.Tot_Item is always undefined in your Navv component, the reason is that a children component cannot modify its parent' state in any way.
Right now your components are structured in the following way: Navbar contains both Navv and Menu. Menu is the one that contains a list of items and is supposed to update the list of selected items, while Navv is just supposed to display the number of items in said array.
However, there's a problem: you cannot pass data directly from Menu to its sibling, Navv. It's necessary to have a parent component (in this case, Navbar) that gets the data from Menu and pass it down to Navv. However, it doesn't really work as you have it. The only way a children component (Menu) can alter the parent' (Navbar) state is by using a callback method, as seen in the example below:
Changes in Navbar component
// CHANGE: Create callback function to send data to parent
updateTotalItems = total => {
this.setState({ Total_Item: total }, () =>
console.log("total items in navbar", this.state.Total_Item)
);
};
// CHANGE: pass callback method as props to the children component that modifies the item counter
render() {
return (
<React.Fragment>
<Navv Tot_Item={this.state.Total_Item} />
<Menu
updateTotalItems={this.updateTotalItems}
Objs_Type={this.state.Category}
/>
</React.Fragment>
);
Changes in Menu component
Listval() {
/* ... */
// CHANGE: No need to call setState multiple times
this.setState(
{
Select_Price: ll,
Select_Item: ll3,
Select_Item_TotalPrice: ll6,
Total_Item: ll7
},
() => {
// CHANGE: Use callback function to send the array length to the parent component
this.props.updateTotalItems(this.state.Total_Item.length);
}
);
}
Here you have a working version of your sandbox example with all of these changes. Hope it helps!

You can do the below changes.
You can place your state in constructor.
You need to declare ListVal using FAT operator and setState of what you like. (In my case I have explicitly set it to 2 on click of OK button in Modal popup and its appearing on the screen too)
import React, { Component } from "react";
import Menu from "./Menu";
import Navv from "./Navv";
class Navbar extends Component {
constructor(props)
{
super(props);
this.state = {
Category: [
{
id: 1,
FoodType: "Chinese",
Menu: ["Egg Drop", "Chicken Fried", "Beef Fried"],
Price: [2, 8, 10]
},
{
id: 2,
FoodType: "Mexican",
Menu: ["Veg Burrito", "Chicken Burrito", "Beef Burrito"],
Price: [7, 8, 10]
}
],
One_Item: [
{
listvalue: null,
Select_Item: null,
Select_Price: null,
Select_Quantity: null,
Select_Item_TotalPrice: null
}
],
Total_Item: 0
};
}
Listval=async()=>{
this.setState({ Total_Item: 2});
}
render() {
return (
<React.Fragment>
<Navv Tot_Item={this.state.Total_Item} />
<Menu
Listvalll={this.Listval}
Objs_Type={this.state.Category}
/>
</React.Fragment>
);
}
}
export default Navbar;

Related

How to avoid unmounting of children components when using JSX's map?

This is a more concise version of a question I raised previously. Hopefully, it's better explained and more understandable.
Here's a small app that has 3 inputs that expect numbers (please disregard that you can also type non-numbers, that's not the point). It calculates the sum of all displayed numbers. If you change one of the inputs with another number, the sum is updated.
Here's the code for it:
import { useCallback, useEffect, useState } from 'react';
function App() {
const [items, setItems] = useState([
{ index: 0, value: "1" },
{ index: 1, value: "2" },
{ index: 2, value: "3" },
]);
const callback = useCallback((item) => {
let newItems = [...items];
newItems[item.index] = item;
setItems(newItems);
}, [items]);
return (
<div>
<SumItems items={items} />
<ul>
{items.map((item) =>
<ListItem key={item.index} item={item} callback={callback} />
)}
</ul>
</div>
);
}
function ListItem(props) {
const [item, setItem] = useState(props.item);
useEffect(() => {
console.log("ListItem ", item.index, " mounting");
})
useEffect(() => {
return () => console.log("ListItem ", item.index, " unmounting");
});
useEffect(() => {
console.log("ListItem ", item.index, " updated");
}, [item]);
const onInputChange = (event) => {
const newItem = { ...item, value: event.target.value };
setItem(newItem);
props.callback(newItem);
}
return (
<div>
<input type="text" value={item.value} onChange={onInputChange} />
</div>);
};
function SumItems(props) {
return (
<div>Sum : {props.items.reduce((total, item) => total + parseInt(item.value), 0)}</div>
)
}
export default App;
And here's the console output from startup and after changing the second input 2 to 4:
ListItem 0 mounting App.js:35
ListItem 0 updated App.js:43
ListItem 1 mounting App.js:35
ListItem 1 updated App.js:43
ListItem 2 mounting App.js:35
ListItem 2 updated App.js:43
ListItem 0 unmounting react_devtools_backend.js:4049:25
ListItem 1 unmounting react_devtools_backend.js:4049:25
ListItem 2 unmounting react_devtools_backend.js:4049:25
ListItem 0 mounting react_devtools_backend.js:4049:25
ListItem 1 mounting react_devtools_backend.js:4049:25
ListItem 1 updated react_devtools_backend.js:4049:25
ListItem 2 mounting react_devtools_backend.js:4049:25
As you can see, when a single input is updated, all the children are not re-rendered, they are first unmounted, then re-mounted. What a waste, all the input are already in the right state, only the sum needs to be updated. And imagine having hundreds of those inputs.
If it was just a matter of re-rendering, I could look at memoization. But that wouldn't work because callback is updated precisely because items change. No, my question is about the unmounting of all the children.
Question 1 : Can the unmounts be avoided ?
If I trust this article by Kent C. Dodds, the answer is simply no (emphasis mine) :
React's key prop gives you the ability to control component instances.
Each time React renders your components, it's calling your functions
to retrieve the new React elements that it uses to update the DOM. If
you return the same element types, it keeps those components/DOM nodes
around, even if all* the props changed.
(...)
The exception to this is the key prop. This allows you to return the
exact same element type, but force React to unmount the previous
instance, and mount a new one. This means that all state that had
existed in the component at the time is completely removed and the
component is "reinitialized" for all intents and purposes.
Question 2 : If that's true, then what design should I consider to avoid what seems unnecessary and causes issues in my real app because there's asynchronous processing happening in each input component?
As you can see, when a single input is updated, all the children are
not re-rendered, they are first unmounted, then re-mounted. What a
waste, all the input are already in the right state, only the sum
needs to be updated. And imagine having hundreds of those inputs.
No, the logs you see from the useEffect don't represent a component mount/unmount. You can inspect the DOM and verify that only one input is updated even though all three components get rerendered.
If it was just a matter of re-rendering, I could look at memoization.
But that wouldn't work because the callback is updated precisely because
items change. No, my question is about the unmounting of all the
children.
This is where you would use a functional state update to access the previous state and return the new state.
const callback = useCallback((item) => {
setItems((prevItems) =>
Object.assign([...prevItems], { [item.index]: item })
);
}, []);
Now, you can use React.memo as the callback won't change. Here's the updated demo:
As you can see only corresponding input logs are logged instead of all three when one of them is changed.
At first let's clarify some terminology:
A "remount" is when React deletes it's internal representation of the component, namely the children ("hidden DOM") and the states. A remount is also a rerender, as the effects are cleaned up and the newly mounted component is rendered
A "rerender" is when React calls the render method or for functional components the function itself again, compares the returned value to the children stored, and updates the children based on the result of the previous render
What you observe is not a "remount", it is a "rerender", as the useEffect(fn) calls the function passed on every rerender. To log on unmount, use useEffect(fn, []). As you used the key property correctly, the components are not remounted, just rerendered. This can also easily be observed in the App: the inputs are not getting reset (the state stays).
Now what you want to prevent is a rerender if the props do not change. This can be achieved by wrapping the component in a React.memo:
const ListItem = React.memo(function ListItem() {
// ...
});
Note that usually rerendering and diffing the children is usually "fast enough", and by using React.memo you can introduce new bugs if the props are changed but the component is not updated (due to an incorrect areEqual second argument). So use React.memo rather conservatively, if you have performance problems.

How to get dynamic prop from clicked element in React

I have a React "tree" menu component which has main links with submenus which are dynamically generated by a JSON get call. In the React Inspector I can see that each element on the tree has several props but when I click on each element the only one I can access is value. Here is the props list:
{
"checked": 0,
"className": null,
"label": "192.168.1.71",
"isLeaf": false,
"isParent": true,
"title": null,
"treeId": "rct-~xDYGzs",
"value": "5bd350bf-8515-4dc2-9b12-16b221505593",
}
Here is the code for accessing the value (which was provided in the component API):
onClick(clicked) {
this.setState({ clicked });
console.log('You clicked on ' + clicked.value);
}
If I substitute any other prop name (like "treeId") for clicked.value I get "undefined". I've tried every variation of e.target and getAttributes but nothing is working. Hopefully this is enough info to get some advice but I can definitely add more if needed. Thanks in advance.
Addendum: This is all being scripted into a pre-existing component called react-checkbox-tree so putting together a Codesandbox would be kind of difficult. I did a console.log(clicked) as suggested and got the following:
{value: "5bd81d67-3fd5-464a-b115-161ce291c2d8", checked: false}
For whatever reason the remaining props besides value and checked are not reporting and I can't access them and I have tried everything imaginable.
this.setState({ clicked }) is shorthand for this.setState({ clicked: clicked }). This is called Object Destructuring. If you change that to anything else, then it will rewrite it to (In the case of treeId): treeId: treeId (The variable being passed in to the onClick function is named clicked, so treeId will be undefined.)
If you want to set treeId to clicked.value, simply use:
this.setState({
treeId: clicked.value
});
You can still use the object destructing in the parameter, if value is on the clicked object, and that's all you care about:
onClick({ value }) {
this.setState({ treeId: value });
console.log(`You clicked on ${value}`);
}
The reason you can only get the [value] prop from your onClick is because other data doesn't get passed along in a mouse event. If you want to return all of the props from your subcomponent you can adjust the way you're writing your click handler to return a function like this:
class MainComponent extends Component {
handleClick(props) {
console.log(props); /* whatever you sent from the sub component */
}
render() {
return (
<SubComponent
subComponentClicked={this.handleClick}
/>
);
}
}
class SubComponent extends Component {
handleClick = () => {
this.props.subComponentClicked(this.props);
}
render() {
return (
<div onClick={this.handleClick}></div>
);
}
}

React component not re-rendering, although Object in props changes

I know, there are many, many similary questions.. **duplicate alarm!**
But: I looked through all of them, I promise. I'm quite sure now, that this is another case, that could have to do with the props being an object (from what I've read here). But I couldn't solve the following, anyway:
class CsvListDropdown extends Component {
constructor(props) {
super(props);
this.state = { sessions: props.sessions }
this csvsInSession = this.csvsInSession.bind(this);
}
csvsInSession(sessions) {
return (sessions
.map(keys => Object.entries(keys)[2][1])
.map((csv, i) => (
<option value={csv} key={i}>{csv}</option>
))
)
}
render() {
const { isLoading } = this.props
if (isLoading) { blablabla.. }
else {
return (
...
<select value={this.props.sessions[0].currentCsv}>
{this.csvsInSession(this.state.sessions)}
</select>
...
)
}
}
}
export default withTracker(() => {
const handle = Meteor.subscribe('sessions');
return {
isLoading: !handle.ready(),
sessions: Sessions.find({}).fetch()
};
})(CsvListDropdown);
Now from the client I am writing another document into the Sessions collection, containing the .csv filename, while this new csv file is being uploaded to a remote server. console.log(this.props.sessions) gives me an array, which is up to date. But the component itself does not re-render.
What I also don't understand is: console.log(this.state.sessions) returns undefined. (note: state)
What I tried so far:
{this.csvsInSession(this.props.sessions)} (note: props)
Adding a withTracker / State / Props to the parent component and passing the sessions object from either state or props as params to the child component, that should re-render.
forceUpdate()
componentWillUpdate()
What may be important as well: The component should re-render about the same time another component also re-renders (which displays the contents of uploaded CSVs, that return from a microservice and get written into another collection). The latter does actually re-render.. But that dropdown does not.. argh!
this.state will only change if you call this.setState(), which you are not doing. You are initializing state with a value from props, but only in the constructor when the component is first instantiated. After that, even if props changes your component may re-render but what it displays won't change because state hasn't been updated.
In fact, there does not appear to be any reason whatsoever to store data in state in that component. It might as well be a functional presentational component:
function CsvListDropdown(props) {
function csvsInSession(sessions) {
return (sessions
.map(keys => Object.entries(keys)[2][1])
.map((csv, i) => (
<option value={csv} key={i}>{csv}</option>
))
)
}
const { isLoading } = props;
if (isLoading) { blablabla.. }
else {
return (
...
<select>
{csvsInSession(props.sessions)}
<select>
...
)
}
}
Generally all of your components should be stateless functional components unless they specifically need to store internal state for some reason.
Now I finally solved it, and it turns out that the component did actually update at any time, but I did not notice it, simply because the latest item in the array was quietly appended to the bottom of the dropdown list. This however I was not expecting, as I had published the collection with a descending sorting.
// server-side
Meteor.publish('sessions', function() {
return Sessions.find({ userId: this.userId }, { sort: {createdAt: -1} });
});
Server-side seems to be the wrong place to sort. It simply does not have an effect. So sorted on the client side, when subscribing:
// client-side
export default withTracker(() => {
const handle = Meteor.subscribe('sessions');
return {
isLoading: !handle.ready(),
sessions: Sessions.find({}, { sort: {createdAt: -1} }).fetch()
};
})(App)
I had omitted an important detail from my question, that is how I set the value of the dropdown field:
<select value={this.props.sessions[0].currentCsv}>
{this.csvsInSession(sessions)}
</select>
So lesson learned: If you think your react component does not re-render, always check if that's true, before assuming so.
As a side effect of debugging I restructered my components. Now the Meteor.subscribe() is within the parent component, that contains all the children, that have to handle the sessions object. And the sessions object gets passed down from the parent to the (grand)children as props. I think it's more readable and easier to maintain that way.

Update dynamic created components

Situation
I have a parent component which renders some child components.
The child component should display information passed from the parent component.
Context
A battleship game with multiplayer:
The parent component contains a field of 10x10 up to 10x30 buttons (child components). If a button is pressed a signal with the position of the button get emitted to the api. The api decides if the pressed button needs to change it color.
Problem
By updating the state of the parent the child components props will not be updated if the child is created dynamically.
Solution Attempts
Dont render Childs dynamically:
Not possible in my case
Use redux
I consider the child as dump/presentational Component because it only displays information. Also in my case there are 100-300 Child Components.
Redux and React
Use refs
There are 100-300 Child Components...Don’t Overuse Refs
Question
What should I do? Is there a solution that is not anti-pattern?
Current Code
class Parent extends React.Component {
constructor(props) {
super(props);
this.state={
foo: 'BAR'
}
this.child=undefined;
}
componentWillMount(){
this.child=<Child foo={this.state.foo}/>
}
componentDidMount(){
var that=this
setTimeout(function(){
that.setState({ //it is just here for demonstration
foo: 'FOO'
})
}, 1000);
}
render() {
return (
<div>
Is: {this.child}
Not Dynamic created: <Child foo={this.state.foo}/>
Actual: <p>{this.state.foo}</p>
</div>
);
}
}
class Child extends React.Component {
render() {
return (
<p>{this.props.foo}</p>
);
}
}
ReactDOM.render(<Parent />, document.getElementById('app'));
<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>
<div id="app"></div>
Code with approach from #RaghavGarg and #Clinton Blackburn
class Parent extends React.Component {
constructor(props) {
super(props);
this.state={
foo: 'BAR'
}
this.child=undefined;
}
componentDidMount(){
var that=this
setTimeout(function(){
that.setState({ //it is just here for demonstration
foo: 'FOO'
})
}, 1000);
}
renderChild(){
return <Child foo={this.state.foo} />
}
render() {
const child=this.renderChild()
return (
<div>
Is: {child}
Not Dynamic created: <Child foo={this.state.foo}/>
Actual: <p>{this.state.foo}</p>
</div>
);
}
}
class Child extends React.Component {
render() {
return (
<p>{this.props.foo}</p>
);
}
}
ReactDOM.render(<Parent />, document.getElementById('app'));
<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>
<div id='app'/>
Like #Clinton said, componentWillMount would be only called once. So you are essentially initiating your this.child variable, but only updating state, you also need to update the value of the variable.
Since you are just making your dynamic components and saving them in the class instance(like this.child). I would recommend you to do the same task in componentWillUpdate. So that whenever your state changes, it will be reflected in your dependent variables. But please make sure not to use setState inside this lifecycle method.
componentWillUpdate() is invoked just before rendering when new props
or state are being received. Use this as an opportunity to perform
preparation before an update occurs.
Also, consider using constructor to initiate state and not use setState in componentWillMount.
componentWillMount() is invoked just before mounting occurs. It is
called before render(), therefore calling setState() synchronously in
this method will not trigger an extra rendering. Generally, we
recommend using the constructor() instead for initializing state.
Reference:
https://reactjs.org/docs/react-component.html#unsafe_componentwillupdate
https://reactjs.org/docs/react-component.html#unsafe_componentwillmount
Update after adding context
So now, I am supposing you will be having a DS(maybe an object or a nested array) in the state in which you will be maintaining the status of each box(child component). You can loop over it and provide a unique key(maybe like {row}-{col}) to every one of them, now you just need to update the state so to reflect the change in the specific child.
Note: Using the unique key to every child will enable react internally optimize the re-render and will not re-render the child(using unique key), which is not changed. See below code for reference.
this.state = {
boxes: {
1: {
1:{ status: true, /* more data */ },
2:{ status: true, /* more data */ },
},
2: {
1:{ status: true, /* more data */ },
2:{ status: true, /* more data */ },
},
3: {
1:{ status: true, /* more data */ },
2:{ status: true, /* more data */ },
},
4: {
1:{ status: true, /* more data */ },
2:{ status: true, /* more data */ },
}
}
};
// this will create a 4x2 box, now you will render using above object
// ...somewhere in your render method
{
Object.keys(this.state.boxes).map(row => (
<div key={`row-${row}`} className="row">
{
Object.keys(this.state.boxes[row]).map(column => (
<Child
key={`box-${row}x${column}`}
data={this.state.boxes[row][column]}
/>
))
}
</div>
))
}
Now, whenever you change the status of say 2x1 box to false, react will only re-render the child with key box-2x1.
Update after OP comment
shouldComponentUpdate should be used to decide whether you want to update the component upon the state changes. It's a lifecycle method of React component. By default it returns true, but you can return false basis on your condition to not update the component. It would definitely help in maintaining the good performance.
Reference: React: Parent component re-renders all children, even those that haven't changed on state change
componentWillMount is only called once when the parent component is inserted into the DOM. It is probably not where you want to create child components that need to be changed after mounting.
See the component lifecycle at https://reactjs.org/docs/react-component.html#the-component-lifecycle.
The simplest solution is to create the child component in the render() method. The child component will be re-created when the parent component is updated.

Rendering a Table of Data in React from JavaScript Object

I am trying to create a React component that renders a table of data from a JavaScript object pixarMovies that I initially set the state of. I would like to render a table of the movie data with movies sorted chronologically by date (I've attached an image of what I'm trying to do that I created in HTML). How might one accomplish this?
This component is separate from App.js, and I will include it in a different component. I would like the table to adjust accordingly to the state (to account for the addition and removal of movies), as well as be able to apply onClick functions to the buttons.
import React, { Component } from 'react';
import { BrowserRouter as Router, Redirect, Switch, Route, Link } from 'react-router-dom';
export default class PixarMovies extends React.Component {
constructor(props) {
super(props);
this.state = {
pixarMovies: [
{
title: "Cars",
date: "2006-06-09",
budget: "$120 million",
},
{
title: "Toy Story",
date: "1995-11-05",
budget: "$30 million",
},
{
title: "Ratatouille",
date: "2007-06-29",
budget: "$150 million",
},
{
title: "The Incredibles",
date: "2004-11-05",
budget: "$92 million"
}
]
}
}
render() {
return (
<div className="container">
<h1>Pixar Movies</h1>
{/* Insert Table here */}
</div>
);
}
}
Thanks!
OK, looks like there are a couple of pieces here, so let's take a look at each of them.
Sorting chronologically by date: Javascript arrays have a sort function that looks like this: myArray.sort(). You can pass a function to the .sort call to use whatever field you want. So maybe something like this for you:
const sortedPixarMovies = this.state.pixarMovies.sort((movA, movB) => new Date(movA.date) - new Date(movB.date));
Rendering a table: There are a bunch of ways to render a table in React. I'd recommend looking at some 3rd party components that will make rendering a table quite simple and effortless. If you want to build one yourself, take a look at something like this guide. A quick google of "render a table in react" should give you all the information you need.
Table should adjust according to state: This should happen automatically. Every time your component's state changes, your component will re-render, so it will re-render the table with the new data.
Apply onClick functions to buttons: I don't see any buttons in your code, but onClick functions work in react pretty much like they would in anything else. Define a function on your component, then pass it to the button. For example:
The function:
myOnChangeFunc = (newValue) => {
this.setState({ myField: newValue });
}
And then the button:
<button onChange={ this.myOnChangeFunc }>My Button</button>
Responding to question below
If you're using a 3rd party component, usually you want to pass your data in as a prop. So your render function might look something like:
render() {
const myFormattedData = this.state.data.map... (formatted);
return <ThirdPartyComponent data = { myFormattedData } />;
}
If you're doing it yourself, you can map the data in your render function. Here's a super simple example of how you would render a list:
render() {
return (
<ul>
{ this.state.listData.map((d) => <li key={ d.id }>{ d.value }</li>) }
</ul>
);
}

Categories