I am repeating a OBJECT to render UI. Each row has a ItemName and a checkbox. Onclick of checkbox I should get the ID of that row. When I Run the page I am getting this error msg => Cannot read property 'addToCompare' of undefined
import React from 'react';
export default class List extends React.Component {
constructor(props) {
super(props);
}
addToCompare(event){
console.log('get id of row');
}
render() {
var planList = [
{id: 1, "itemname": "Sunshine},
{id: 2, "itemname": "Global"},
{id: 3, "itemname": "Lifetime"}
];
return (
<div id="layout-content" className="layout-content-wrapper">
{
planList.map(function(item, i) {
return (
<div className="row" key={i}>
<h1>
<input type="checkbox" onChange={this.addToCompare.bind(this)} />{item.name}
</h1>
</div>
)
})
}
)
}
Use es6 syntax, for map function to refer this to List as
planList.map((item, i) =>{
return (
<div className="row" key={i}>
<h1>
<input type="checkbox" onChange={this.addToCompare.bind(this)} />{item.name}
</h1>
</div>
)
})
or store this to other variable and use it to get addToCompare property as stated by #Marco
And If you're using Class declarations, you can use all ES6 features.
class Example extends React.Component {
constructor(props) {
super(props)
this.addToCompare = this.addToCompare.bind(this)
}
addToCompare(event) {
console.log('get id of row')
}
render() {
const planList = [
{ id: 1, itemname: 'Sunshine' },
{ id: 2, itemname: 'Global' },
{ id: 3, itemname: 'Lifetime' },
]
const rows = planList.map((item, i) =>
<div className="row" key={i}>
<h1>
<input type="checkbox" onChange={this.addToCompare} />
{item.itemname}
</h1>
</div>
)
return (
<div id="layout-content" className="layout-content-wrapper">
{rows}
</div>
)
}
}
ReactDOM.render(
<Example />,
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" />
You are passing wrong context to the onChange method
class Example extends React.Component {
constructor(props) {
super(props);
}
addToCompare(event) {
console.log('get id of row');
}
render() {
var planList = [{
id: 1,
"itemname": "Sunshine"
}, {
id: 2,
"itemname": "Global"
}, {
id: 3,
"itemname": "Lifetime"
}, ];
var _this = this
var rows = planList.map(function(item, i) {
return (
<div className="row" key={i}>
<h1>
<input type="checkbox" onChange={_this.addToCompare.bind(_this)} />{item.itemname}
</h1>
</div>
)
}
)
return (
<div id="layout-content" className="layout-content-wrapper">
{rows}
</div>
)
}
}
ReactDOM.render(
<Example />,
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" />
When you map the list to <div> tags using the map method, the value of this inside that mapping function is undefined, not the component, as it is invoked as an anonymous function.
Inside your render method, declare a that variable, to hold a reference to your component.
var planList = ... ,
that = this;
Now you can reference the component using that instead of this
<input type="checkbox" onChange={that.addToCompare.bind(that)} />{item.name}
Related
I'm trying to display my AboutPageContent.js to AboutPage.js. I would assume I would have to map it out somehow, but I'm not sure how.
Relevant Code
AboutPage.js
import React from 'react';
// CSS import statements
import '../css/AboutPage.css';
import '../css/App.css';
// Content import Statements
import AboutPageContent from '../content/AboutPageContent.js';
class About extends React.Component {
constructor(props) {
super(props);
this.state = AboutPageContent;
}
render() {
return(
<div className='about-page'>
<div className='page-header'>{this.state.about.name}</div>
<div>{this.state.about.desc.map(paragraph => <p>{paragraph}</p>)}</div>
</div>
)
}
}
export default About;
AboutPageContent.js
let AboutPageContent = {
about: [{
name: 'About Me',
desc: [
'p1',
'p2',
'p3',
'p4',
'p5',
'p6'
],
id: 1
}]};
export default AboutPageContent;
You have to do 2 maps, 1 for state.about and another one for state.about[i].desc.
class About extends React.Component {
constructor(props) {
super(props);
this.state = AboutPageContent;
}
render() {
return (
<div className="about-page">
<div className="page-header">{this.state.about.name}</div>
<div>
{this.state.about.map((currAbout) =>
currAbout.desc.map((paragraph, i) => <p key={i}>{paragraph}</p>)
)}
</div>
</div>
);
}
}
Or if you want to display the current about.name, move the <div className='page-header'>... inside this.state.about loop.
class About extends React.Component {
constructor(props) {
super(props);
this.state = AboutPageContent;
}
render() {
return (
<div className="about-page">
<div>
{this.state.about.map((currAbout, i) => (
<React.Fragment key={i}>
<div className="page-header">{currAbout.name}</div>
{currAbout.desc.map((paragraph, i) => (
<p key={i}>{paragraph}</p>
))}
</React.Fragment>
))}
</div>
</div>
);
}
}
Your this.state.about is an array, so you can't reference the name and desc properties with dot notation. Currently, this.state.about.name would be undefined. this.state.about[0].name would be equal to 'About Me'.
I would simply remove the [ ]s around the this.state.about property. That would make it an object (as opposed to an array) so the rest of your code should work fine. Leave this.state.about.desc as an array so you can use the map method.
You can access the object in about this way state.about[0]
const AboutPageContent = {
about: [
{
name: "About Me",
desc: ["p1", "p2", "p3", "p4", "p5", "p6"],
id: 1,
},
],
};
class About extends React.Component {
constructor(props) {
super(props);
this.state = AboutPageContent;
}
render() {
return (
<div className="about-page">
<div className="page-header">{this.state.about[0].name}</div>
<div>
{this.state.about[0].desc.map((paragraph) => (
<p>{paragraph}</p>
))}
</div>
</div>
);
}
}
ReactDOM.render(
<About />,
document.getElementById('root')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root"></div>
However if you have more than one property in the object and you want to display description by name you could try this
const AboutPageContent = {
about: [
{
name: "About Me",
desc: ["p1", "p2", "p3", "p4", "p5", "p6"],
id: 1,
},
{
name: " Section 2",
desc: ["p12", "p10"],
id: 1,
},
],
};
class About extends React.Component {
state = AboutPageContent;
render() {
return (
<div className="about-page">
<div className="page-header">
{Object.values(this.state)
.flat()
.map((o, i) => (
<React.Fragment key={i}>
<p>{o.name}</p>
{o.desc.map((paragraph) => (
<p>{paragraph}</p>
))}
</React.Fragment>
))}
</div>
</div>
);
}
}
ReactDOM.render(
<About />,
document.getElementById('root')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root"></div>
class Services extends Component {
constructor(props) {
super(props);
this.state = {showoffer: false};
}
showOffers=( )=>{
this.setState({showoffer: !this.state.showoffer});
}
render() {
return (
<div className="OSServicesContainer">
<img className="OSlogomark" src={logomark} alt="logo mark" />
<article className="OssHeadingText">OOM INTERIORS OFFERS</article>
{offersdata.map((offers,index)=>{
return ( <div key={index} className="OssoffersContainermain">
<div className="OssoffersContainer">
<div className="OssofferHeadingmain">
<article className="OssofferHeading">{offers.heading}</article>
</div>
<article className="OssofferText">{offers.subheading}</article>
<div className="OssofferViewbtnmain">
<article key={index} className="OssofferViewbtn" onClick={this.showOffers}>{this.state.showoffer?"View Less":"View More"}</article>
</div>
</div>
{!this.state.showoffer?
null:
<div className="OssOfferSubCompmain">
{offers.offersub.map((offer,key) =>{
return <OssOfferSubComp ofrtext={offer.text} ofrsubtext={offer.subtext} />
})}
</div>}
</div>
)
})}
</div>);
}
}
export default Services;
Above is my code
i want to call showoffer function and update only that element clicked
please what shall i do it is triggering all elements
how to trigger single element??
You can try something like this:
`class Services extends Component {
constructor(props) {
super(props);
this.state = {showoffer: 0};
}
showOffers = ( offerIndex ) => {
this.setState({showoffer: offerIndex});
}
hideOffers = () => {
this.setState({showoffer: 0});
}
render() => {
...
<div className="OssofferViewbtnmain">
<article key={index} onClick={ () => this.showOffers(index) }>
{this.state.showoffer?"View Less":"View More"}
</article>
</div>
...
{
this.state.showOffer && this.state.showOffer === index
? // then show it
: ''
}
}`
Hey if you wish to have multiple items open at the same time you can do something like this where you mutate the mapped item to track show hide state. I have added a visible property to the list item that keeps track if the item is open or closed:
import React, { Component } from "react";
import ReactDOM from "react-dom";
import "./styles.css";
class App extends Component {
state = {
items: [
{ header: "Test 1", extra: "Some extra content A" },
{ header: "Test 2", extra: "Some extra content B" },
{ header: "Test 3", extra: "Some extra content C" }
]
};
onItemClick(index) {
const selected = this.state.items[index];
this.setState({
items: [
...this.state.items.slice(0, index),
{ ...selected, visible: !selected.visible },
...this.state.items.slice(index + 1)
]
});
}
render() {
return (
<div>
<ul>
{this.state.items.map((item, index) => {
return (
<li
key={index}
style={{ cursor: "pointer" }}
onClick={() => this.onItemClick(index)}
>
<h3>{item.header}</h3>
{item.visible ? <div>{item.extra}</div> : null}
</li>
);
})}
</ul>
</div>
);
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
https://codesandbox.io/s/busy-germain-hdmrn
I'm building a shopping cart application and I ran into a problem where all my inputs have the same state value. Everything works fine but when I type in one input box, it's the same throughout all my other inputs.
I tried adding a name field to the input and setting my initial state to undefined and that works fine but the numbers don't go through.
How do we handle inputs to be different when they have the same state value? Or is this not possible / dumb to do?
class App extends Component {
state = {
items: {
1: {
id: 1, name: 'Yeezys', price: 300, remaining: 5
},
2: {
id: 2, name: 'Github Sweater', price: 50, remaining: 5
},
3: {
id: 3, name: 'Protein Powder', price: 30, remaining: 5
}
},
itemQuantity: 0
},
render() {
return (
<div>
<h1>Shopping Area</h1>
{Object.values(items).map(item => (
<div key={item.id}>
<h2>{item.name}</h2>
<h2>$ {item.price}</h2>
{item.remaining === 0 ? (
<p style={{ 'color': 'red' }}>Sold Out</p>
) : (
<div>
<p>Remaining: {item.remaining}</p>
<input
type="number"
value={ itemQuantity }
onChange={e => this.setState({ itemQuantity: e.target.value})}
placeholder="quantity"
min={1}
max={5}
/>
<button onClick={() => this.addItem(item)}>Add To Cart</button>
</div>
)}
</div>
))}
</div>
)
}
}
If you are using same state key for all input, All input take value from one place and update to one place. To avoid this you have to use separate state. I suppose you are trying to show input for a list of item.
To achive you can create a component for list item and keep state in list item component. As each component have their own state, state value will not conflict.
Here is an example
class CardItem extends Component {
state = {
number: 0
}
render() {
render (
<input type="text" value={this.state.number} onChange={e => this.setState({ number: e.target.value })} />
)
}
}
class Main extends Component {
render () {
const list = [0,1,2,3,4]
return (
list.map(item => <CardItem data={item} />)
)
}
}
This is a solution which the problem is loosely interpreted, but it does work without having to create another component. As you know, you needed to separate the state of each items in the cart. I did this by dynamically initializing and setting the quantity states of each item. You can see the state changes with this example:
class App extends React.Component {
constructor(props) {
super(props);
this.state = { quantities: {} }
}
componentDidMount() {
let itemIDs = ['1', '2', '3', 'XX']; //use your own list of items
itemIDs.forEach(id => {
this.setState({quantities: Object.assign(this.state.quantities, {[id]: 0})});
})
}
render() {
let list = Object.keys(this.state.quantities).map(id => {
return (
<div>
<label for={id}>Item {id}</label>
<input
id={id}
key={id}
type="number"
value={this.state.quantities[id]}
onChange={e => {
this.setState({quantities: Object.assign(this.state.quantities, {[id]: e.target.value})})
}}
/>
</div>
);
})
return (
<div>
{list}
<div>STATE: {JSON.stringify(this.state)}</div>
</div>
);
}
}
ReactDOM.render(<App/>, document.getElementById('root'));
<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='root'></div>
You can modify the state structure to your liking.
Here is how I usually handle this scenario. You say that you get an array of items? Each item object should contain a key to store the value (count in my example). You can use a generic onChange handler to update an individual item in the array. So now, your state is managing the list of items instead of each individual input value. This makes your component much more flexible and it will be able to handle any amount of items with no code changes:
const itemData = [
{ id: 0, count: 0, label: 'Number 1' },
{ id: 1, count: 0, label: 'Number 2' },
{ id: 2, count: 0, label: 'Number 3' },
{ id: 3, count: 0, label: 'Number 4' }
];
class App extends React.Component {
state = {
items: itemData
}
handleCountChange = (itemId, e) => {
// Get value from input
const count = e.target.value;
this.setState( prevState => ({
items: prevState.items.map( item => {
// Find matching item by id
if(item.id === itemId) {
// Update item count based on input value
item.count = count;
}
return item;
})
}))
};
renderItems = () => {
// Map through all items and render inputs
return this.state.items.map( item => (
<label key={item.label}>
{item.label}:
<input
type="number"
value={item.count}
onChange={this.handleCountChange.bind(this, item.id)}
/>
</label>
));
};
render() {
return (
<div>
{this.renderItems()}
</div>
)
}
}
ReactDOM.render(<App />, document.getElementById('root'));
label {
display: block;
}
<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="root"></div>
You can't use the same state for the both inputs. Try to use a different state for each one like that:
class App extends Component {
state = {
number: ""
}
render() {
return (
<div>
<input
type="number"
value={this.state.number}
onChange={e => this.setState({ number: e.target.value })}
/>
<input
type="number"
value={this.state.number2}
onChange={e => this.setState({ number2: e.target.value })}
/>
</div>
)
}
}
I am trying to recursively render JSON data to nested list using React. Right now I am using simple data object like this:
[{"id": "1",
"name": "Luke"
},
{"id": "2",
"name": "Jim",
"childNodes":[{
"id": "3",
"name": "Lola"
}]
}]
using this class:
export default class NestedList extends Component {
constructor(props) {
super(props);
this.state = {
visible: true
};
}
toggle = () => {
this.setState({ visible: !this.state.visible });
};
renderChild = (child) => {
if (child.childNodes) {
return (
<ul>
{child.myData.map(item => {
return this.renderChild(item);
})}
</ul>
);
}
else if (child.name) {
return <input type="checkbox"><Child name={child.name}/></input>;
}
return null;
}
render() {
return (
<aside>
<div>
<h4>Data Sets</h4>
<ul>
{this.renderChild(this.props.myData)}
</ul>
</div>
</aside>
);
}
}
which calls a Child class that creates list element:
export default class Child extends Component {
render() {
let {name}=this.props;
return (
<li>{name}</li>
);
}
}
but it doesn't print anything. I have tried removing attribute childNodes altogether and tried to print the list but it doesn't work still. I don't understand where I am doing wrong. I would appreciate some help regarding how to fix this.
You need to map through myData first so the rendering process begins:
<ul>
{this.props.myData.map(data => this.renderChild(data))}
</ul>
Also, on childNodes you need to loop through child.childNodes:
if (child.childNodes) {
return (
<ul>
{child.childNodes.map(node => this.renderChild(node))}
</ul>
);
}
there were couple of issues here:
You passed myData to renderChild which doesn't hold childNodes
property nor name property. Hence none of the conditions were met
(null was returned).
So maybe you should loop through myData and
pass each member of the array to renderChild.
Even if we will pass a valid "child" to the renderChild method,
inside this condition:
if (child.childNodes) {
Again you are using a wrong property:
<ul>
{child.myData.map(item => {
return this.renderChild(item);
})}
</ul>
this should be:
{child.childNodes.map(item => {...
Last thing, You can't nest child elements inside an input element.
so change the layout, maybe like this? :
<input type="checkbox"/>
<Child name={child.name} />
Here is a running example with your code:
const data = [
{
id: "1",
name: "Luke"
},
{
id: "2",
name: "Jim",
childNodes: [
{
id: "3",
name: "Lola"
}
]
}
];
class NestedList extends React.Component {
constructor(props) {
super(props);
this.state = {
visible: true
};
}
toggle = () => {
this.setState({ visible: !this.state.visible });
};
renderChild = child => {
if (child.childNodes) {
return (
<ul>
{child.childNodes.map(item => {
return this.renderChild(item);
})}
</ul>
);
} else if (child.name) {
return (
<div>
<input type="checkbox"/>
<Child name={child.name} />
</div>
);
}
return null;
};
render() {
return (
<aside>
<div>
<h4>Data Sets</h4>
<ul>{this.props.myData.map(item => this.renderChild(item))}</ul>
</div>
</aside>
);
}
}
class Child extends React.Component {
render() {
let { name } = this.props;
return <li>{name}</li>;
}
}
ReactDOM.render(<NestedList myData={data} />, document.getElementById("root"));
<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="root"></div>
I have a parent component that holds a number of children components. I want to add an active className to a a child component when it is clicked on.
This is working, but the issue is that each child component can have an active classname. Only one of the components should have be active each time.
Does anyone have any idea how to solve this issue?
Please see my code below.
class MyComponent extends Component {
constructor(props) {
super(props);
this.addActiveClass= this.addActiveClass.bind(this);
this.state = {
active: false,
};
}
addActiveClass() {
const currentState = this.state.active;
this.setState({ active: !currentState });
};
render() {
return (
<div
className={this.state.active ? 'active': null}
onclick={this.addActiveClass}
>
<p>{this.props.text}</p>
</div>
)
}
}
class Test extends Component {
render() {
return (
<div>
<MyComponent text={'1'} />
<MyComponent text={'2'} />
<MyComponent text={'3'} />
<MyComponent text={'4'} />
</div>
);
}
}
Add the active functionality to your Test Component.
There you can check whether there is already active Component or not.
class MyComponent extends React.Component {
render() {
return <div
id={this.props.id}
className={this.props.active ? 'active': null}
onClick={this.props.handleClick} >
{this.props.text}
</div>
}
}
class Test extends React.Component {
constructor(props) {
super(props);
this.state = {
components: [
{id: 1, text: 1},
{id: 2, text: 2},
{id: 3, text: 3},
{id: 4, text: 4}
],
activeID: null
};
}
handleClick(e) {
// If there is already active component ID, don't set another one!
// We support only one active ID.
if (this.state.activeID !== null) return;
const id = parseInt(e.target.id);
this.setState({
activeID: id
});
}
renderComponents() {
return (this.state.components.map( c =>
<MyComponent
id={c.id}
active={c.id === this.state.activeID}
text={c.text}
handleClick={this.handleClick.bind(this)} /> ));
}
renderActiveIDText() {
return (this.state.activeID ? <p>{"Active Component ID: " + this.state.activeID}</p> : null );
}
render() {
return <div>
{this.renderActiveIDText()}
{this.renderComponents()}
</div>
}
}
ReactDOM.render(<Test />, document.getElementById('container'));
<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="container">
<!-- This element's contents will be replaced with your component. -->
</div>