How to get dynamic prop from clicked element in React - javascript

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>
);
}
}

Related

How to pass state values from a component into another child component that contains param settings?

I have a basic component that goes out and gets user info via axios and then sets the users state. But in the component, I have another nested component that is a form type component that sets placeholders, defaultValue, etc.
This is the lifecyle method that gets the data and sets state:
componentDidMount() {
axios.get('https://niftyURLforGettingData')
.then(response => {
console.log(response.data);
const users = response.data;
this.setState({ users });
})
.catch(error => {
console.log(error);
});
}
Nested within this component is my form component:
<FormInputs
ncols={["col-md-5", "col-md-3", "col-md-4"]}
properties={[
{
defaultValue: "I NEED VALUE HERE: this.state.users.id",
}
/>
if I use just:
{this.state.users.id} outside the component it works... but inside form...nothing.
I am quite sure I have to pass the state into this component... but can't quite get it.
I am pretty sure it doesn't work because users is undefined when your component renders for the first time.
Try to initialize that variable in the state doing something like this:
state = {
users: {}
}
and then use a fallback since id will also be undefined doing this:
<FormInputs
ncols={["col-md-5", "col-md-3", "col-md-4"]}
properties={[
{
defaultValue: this.state.users.id || "Fallback Value", // this will render "Fallback value" if users.id is undefined
}
/>
If this is not the case please share more information about your situation.

props is undefined although state has changed

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;

Propogating Events to Child Components in React

I want to send events down to my React child.
I feel like this is kind of an easy thing to do, so maybe i just have a mental block, and there is something obvious that is staring me in the face.
Anyway, I have a little Test app which illustrates the problem:
export class Test extends React.Component {
constructor(props) {
super(props);
this.state = {};
}
render() {
let {buttonClicked, textFieldChanged} = this.state
return (
<div>
<button onClick={()=>this.handleClick()}>
Click
</button>
<input type={"text"} onChange={()=>this.handleTextChange()}/>
<Inner buttonClicked={buttonClicked} textFieldChanged={textFieldChanged}/>
</div>
);
}
handleClick(e) {
this.setState({ buttonClicked: true })
}
handleTextChange(e) {
this.setState({textFieldChanged:true})
}
}
class Inner extends React.Component {
render() {
let {buttonClicked, textFieldChanged} = this.props;
return (
<React.Fragment>
<div>Clicked : {buttonClicked ? "CLICKED!" : " "}</div>
<div>Text input : {textFieldChanged ? "TYPED!" : " "}</div>
</React.Fragment>
);
}
}
A button and a textfield live in the parent. Both these widgets can fire off events and change the child component.
This is simply achieved by passing a state value as a property down to the child. Very easy stuff.
However I would like an either/or situation. When I click the button this removes the text event, and vice versa. Ie. I do not want to see a situation like this :
Now there is a very obvious way to fix this by changing the state value to "false" of the other value.
handleClick(e) {
this.setState({ buttonClicked: true, textFieldChanged: false })
}
handleTextChange(e) {
this.setState({textFieldChanged:true, buttonClicked: false})
}
Is there any OTHER way of doing this?
The problem is that I have LOTS and LOTS of even handlers in my component and I don't want to negate the other state properties of the other values.
if i understood you correctly just one function will help - pass the attribute name into it
handleClick(propName) {
this.setState({
...this.state,
[propName]: !this.state[propName]
})
}
Create property lastEventType in parent component state , whenever you click or type - update it. And pass only this property to Inner component

How to setState from within a mapped item within React component

I'm trying to change the state of a component that is part of a mapped array of objects from a json file. I want to ONLY change the item containing the clicked button and none of the others.
I've been attempting to set a property (projectID) with an onClick and while I can get it to toggle one element of state (expanded or not expanded), it does it to ALL the returned results. So I've been trying to get the projectId (set in the data) and use that to set a conditional. But I can't seem to get projectId to update with the click. I briefly played around with context but I think there's something simpler I'm missing. I've attempted it within the onClick (as shown) and from within onViewChange, but that didn't seem to work as I can't access item.id from outside the mapped item.
I'm using a conditional based on a prop handed down from a couple levels up to set the category, showing only the objects I want. That part seems to be working. So I removed my expanded state as it's not necessary to the issue here.
import React from 'react';
import projects from '../data/projects.json';
class ProjectCard extends React.Component {
constructor(props) {
super(props);
this.state={
projects,
expanded: false,
projectId: -1
}
}
onViewChange = (e) => {
this.setState({
expanded: !this.state.expanded
});
console.log(this.state.projectId)
};
render() {
return (
<React.Fragment>
{this.state.projects.map((item) => {
if (!this.state.expanded && item.category === this.props.category) {
return (
<div key={item.id} className="project-card">
<div className="previewImg" style={{backgroundImage: `url(${item.previewImg}` }} ></div>
<div className="copy">
<h2>{item.title}</h2>
<p>{item.quickDesc}</p>
</div>
<div className="footer">
{item.tools.map((tool) => {
return (
<div key={tool} className="tools" style={{backgroundImage: `url(${tool}` }}></div>
);
}
)}
<button onClick={() => this.onViewChange({projectId: item.id})} className="btn float-right"><i className="fas fa-play"></i></button>
</div>
</div>
);
}
}
)}
</React.Fragment>
);
};
};
export default ProjectCard;
I've set a console log to tell me if projectId changes and it always comes back as my default value (-1). The pasted version is the only one that doesn't throw errors with regards to undefined values/objects, but still no changes. If I can get projectId to change based on the item.id from the clicked button, I think I can figure out the conditional and take it from there.
You aren't actually setting the state with the new projectId in your click handler. First step is just simply passing the item's ID to the click handler, not an object:
onClick={() => this.onViewChange(item.id)}
And second part is to actually use that argument in your click handler:
onViewChange = (id) => {
this.setState({
expanded: !this.state.expanded,
projectId: id
});
};
Also, setState() is asynchronous so you can't do what you did and console.log your state on the next line and expect it to have changed. Log the state at the top of your render function to see when it changes (after state/props change, render is called). Or another option is to use the optional second argument of setState, which is a callback that's executed with the new state:
this.setState({id: 5}, () => console.log(this.state.id)) <-- id will be 5

React How to fire onClick each from map function

I have some lists which are mapped and I'd like to fire onClick when I click an element(which is one of objects from the array).
Let's say there are 3 lists and when I click one of them, I'd like to change "the element that i clicked"'s class to 'open' from 'close'.
state= {
open: false
}
handleClick = () => {
this.setState({
open: !this.state.open
})
}
Array.map((list, index) => {
<div key={index} onClick={this.handleClick}>
<p className={this.state.open? 'open' : 'close'}>{list.title}</p>
</div>
})
.
Array = [{
title: 'a',
link: '/service/wallet'
},{
title: 'b',
link: '/service/home'
}]
I have a value of this.props.location.pathname and I think I can compare this to Array[i].link.
something like this?
if(this.props.location.pathname === Array[0].link){
}
However, I don't know how to complete the code for this issue. please tell if my idea is right and some hints.
You'll need to keep the "is it clicked?" information in this.state. Because it's a list of things you need to keep track of you can't store the state in a single variable, you'll need a map of them.
state= {
open: {}
}
handleClick = (link) => {
let linkOpenState = false;
if (this.state.open.hasOwnProperty(link)) {
linkOpenState = this.state.open[link];
}
this.setState({ open: { [link]: linkOpenState } })
}
Array.map((list, index) => {
<div key={index} onClick={this.handleClick.bind(this, list.link)}>
<p className={this.state.open[list.link]? 'open' : 'close'}>{list.title}</p>
</div>
})
You need to get used to thinking "the React way". State needs to be kept in component state (or if complexity becomes a problem then look at Redux). You don't imperatively manipulate the DOM and you don't "ask" the DOM for information, you tell it how it should look based on the state.
Please have a look at this code mapping data with react elements
To solve this problem, you can have a state which tracks the interaction for a group of elements, in your case it is list of elements.
here you can use a map in a state variable and have {key,value} pair associated with each element to track it.
Hope this helps.
Thanks.

Categories