I am new to React. In fact I am new to any frontend programming lanugage. Therefore I encounter many really weird and sometimes even hilarious problems. I am struggling with sending an array to another compontent. The problem is user creates that array, and it's created dynamically inside render(){return(..)}
class Home extends Component {
constructor(props) {
super(props)
this.chosenItems = [];
}
state = {
items: [],
};
// code to import JSON from backend API, irrelevant, that part works fine
addItem(item){
this.chosenItems.push(item);
console.log(this.chosenItems); //logs created array, everything works like a charm
}
render() {
const {items} = this.state;
return (
//some code
<div key={item.id}>
{item.name} {item.price}<img src = {item.url} className="photo"/><button onClick={() => this.addItem(item)}>ADD</button>
</div>
<Basket dataFromParent = {this.getItems} />
</div>
and Basket class
class Basket extends React.Component {
constructor(props) {
super(props);
this.chosenItems = [];
}
state = {
items: []
};
componentDidUpdate()
{
this.chosenItems = this.props.dataFromParent;
console.log(this.props.dataFromParent);
}
render() {
return (
<div>
<h2>{this.chosenItems}</h2>
<h2>{this.props.dataFromParent}</h2>
</div>
);
}
}
export default Basket;
the problem is console log shows "undefined". Could you tell me what I am doing wrong? Or maybe my entire approach is incorrect and I should look for another solution?
Update
class Home extends Component {
state = {
items: [],
chosenItems []
};
// code to import JSON from backend API, irrelevant, that part works fine
addItem(item){
this.setState(prev => ({
chosenItems: [...prev.chosenItems, item]
}))
}
render() {
const {items, chosenItems} = this.state;
return (
<div>
<div><Basket chosenItems ={this.state.chosenItems} /></div>
<Router>
<div className="container">
<ul>
<Link to="/login">login</Link>
<Link to="/basket">basket</Link>
</ul>
<Route path="/login" component={Login} />
<Route path="/basket" component={Basket} />
</div>
</Router>
<div>
{items.map(item =>
<div key={item.id}>
{item.name} {item.price} {item.quantity} <img src = {item.url} className="photo"/><button onClick={() => this.addItem(item)}>Add!</button>
</div>
)}
</div>
</div>
);
}
}
class Basket extends React.Component {
render() {
return (
<div>
{this.props.chosenItems.map(item =>
<div key={item.id}>
{item.name}{item.price}
</div>
)}
</div>
);
}
}
and that works, but the chosenItems array is printed immediatelty where
<Basket chosenItems ={this.state.chosenItems} />
is located after the button is pressed. And when I click on basket redirection I get
TypeError: Cannot read property 'map' of undefined
Firstly, you must understand that things that you don't set in state don't cause a re-render and hence an updated data isn't reflected on to the UI or passed onto the children.
Secondly, you do not need to store the data passed from parent in child again, you can directly use it from props
class Home extends Component {
state = {
items: [],
chosenItems []
};
// code to import JSON from backend API, irrelevant, that part works fine
addItem(item){
this.setState(prev => ({
chosenItems: [...prev.chosenItems, item]
}))
}
render() {
const {items} = this.state;
return (
<div>
//some code
<div key={item.id}>
{item.name} {item.price}<img src = {item.url} className="photo"/><button onClick={() => this.addItem(item)}>ADD</button>
</div>
<Basket chosenItems ={this.state.chosenItems} />
/div>
)
}
}
class Basket extends React.Component {
render() {
return (
<div>
<h2>{this.props.chosenItems.map(item=> <div>{item.name}</div>)}</h2>
</div>
);
}
}
export default Basket;
I see a couple of problems in the snippet -
You are passing this.getItems to your child component as props. It's never defined in the parent. I think it should have been items array state that you have created.
chosenItems should have been a state and you should dig deeper on how to update a state. There is a setState function, learn abt it.
In child, again the the constructor is written like parent's with chosenItems and items which is not needed. You can use them from props.
Please have a look on https://reactjs.org/docs/react-component.html#setstate how to mutate the state of a component. You will find few more basics over this document.
The reason you are getting undefined in the log is because the this.getItems() in Home component is returning undefined either there is no such method or probably the state variable itself is undefined.
In a nutshell few things:
When you want to pass an array to child component, it is as simple as passing any object or property For eg. (I am hoping you want to pass the choosen items to Basket component)
Always initialise state in constructor.
so chooseItems and items should be a part of state and inside constructor.
Your code should look like:
class Home extends Component {
constructor(props) {
super(props)
this.state = {
chosenItems: [],
items: [],
}
}
addItem(item){
this.setState({
chosenItems: [...this.state.chosenItems, item]
})
console.log(this.chosenItems); //logs created array, everything works like a charm
}
render() {
const {items, chooseItems} = this.state;
return (
items.map(item => {
return (
<div key={item.id}>
{item.name} {item.price}<img src = {item.url} className="photo"/>
<button onClick={() => this.addItem(item)}>ADD</button>
</div>
)
})
<Basket dataFromParent={chooseItems} />
div>
)
}
}
and the Basket component would not need constructor since the required data is coming from parent component:
class Basket extends React.Component {
render() {
return (
<div>
{this.props.dataFromParent.map(item => <h2>{this.props.dataFromParent}</h2>)}
</div>
);
}
}
export default Basket;
Related
Here's the code for Panel
`
import React from "react";
// import {render} from "react-dom";
import AddInventory from "components/AddInventory";
class Panel extends React.Component{
constructor(props) {
super(props);
this.state = {
activeIndex: ''
}
}
componentDidMount() {
this.activePanel();
}
closePanel=()=>{
this.setState({
activeIndex : false
})
}
activePanel = ()=>{
this.setState({
activeIndex : true
})
}
render(){
return(
<div>
{/*<button className={"button is-primary add-btn"} onClick={this.activePanel}>add</button>*/}
<div className={this.state.activeIndex ? 'panel-wrapper active':'panel-wrapper'}>
<div className={"over-layer"}>
<div className={"panel"}>
<div className={"head"}>
<span onClick={this.closePanel} className={"close"}>x</span>
<AddInventory></AddInventory>
</div>
</div>
</div>
</div>
</div>
)
}
}
export default Panel;
Products:
import React from "react";
import ToolBox from "components/ToolBox";
import Product from "components/Product";
import axios from 'components/axios'
import {CSSTransition , TransitionGroup} from 'react-transition-group'
import Panel from "components/Panel";
class Products extends React.Component{
product =[];
source =[];
state ={
product : [{
id:'1',
name:'Air Jordan1',
tags:'45 colours',
image:'images/1.jpg',
price:'21000',
status:'available'
},
{
id:'2',
name:'Nike Pual George PG 3',
tags:'45 colours',
image:'images/2.jpg',
price:'11000',
status:'available'
},
{
id:'3',
name:'Jordan Why Not Zer0.2',
tags:'10 colours',
image:'images/3.jpg',
price:'15000',
status:'unavailable'
},
]
}
componentDidMount() {
// fetch('http://localhost:3003/products').then(response => response.json()).then( data=>{
// console.log(data)
// this.setState({
// product : data
// })
// })
axios.get('/products').then(response => {
this.setState( {
product : response.data,
source : response.data
})
})
}
search = text=>{
//1.get a new array from product
let _product = [...this.state.source]
//2.filter the array
let res = _product.filter((element)=>{
return element.name.toLowerCase().includes(text.toLowerCase())
})
//set state
this.setState({
product : res
})
}
add = ()=>{
let panel = new Panel(this.props)
panel.activePanel()
}
// add =()=>{
// panel.setState({
// activeIndex : true
// })
// }
render() {
return(
<div>
<ToolBox search={this.search}/>
<div className={'products'}>
<div className="columns is-multiline is-desktop">
<TransitionGroup component={null}>
{
this.state.product.map(p=>{
return (
<CSSTransition
timeout={400}
classNames="product-fade"
key={p.id}
>
<div className="column is-3" key={p.id}>
<Product product={p}/>
</div>
</CSSTransition>
)
})
}</TransitionGroup>
{/*<div className="column is-3">*/}
{/* <Product/>*/}
{/*</div>*/}
{/*<div className="column is-3">*/}
{/* <Product/>*/}
{/*</div>*/}
</div>
<button className={"button is-primary add-btn"} onClick={this.add}></button>
</div>
</div>
)
}
}
export default Products;
I was trynna use activePanel() in Products but it gives me : Warning: Can't call setState on a component that is not yet mounted. This is a no-op, but it might indicate a bug in your application. Instead, assign tothis.statedirectly or define astate = {};` class property with the desired state in the Panel component.
I tried initialize a new panel() but it still gives me the same error.
welcome. I don't think this approach is best practice. Generally, components should only ever be updating their own state (see here) and typically you want data to flow from parent component to child component (see here). Additionally, your design is deceptive. When you render a component, you declare it as JSX in some render (or return) statement. But here, Panel is never formally instantiated in JSX.
In Panel, I would suggest watching a prop such as active via shouldComponentUpdate and updating state based on changes to that prop. Then in Products you can instantiate an instance of Panel in JSX and dynamically set the value of that prop.
I've been at this awhile and I can't get my head around it. I feel so stupid. Can anyone tell me what's wrong?
The console log works currently, and the console.log of the Object states that the state has been updated from false to true.
class ChoiceBar extends React.Component {
constructor() {
super();
this.state = {
handleFirstQuestion: false,
};
this.handleFirstQuestion = this.handleFirstQuestion.bind(this)
}
handleFirstQuestion() {
console.log("Start Rendering First Question")
this.setState({handleFirstQuestion: true}, () => {console.log(this.state);
});
}
render() {
return (
<div>
<center>
<div>
<button onClick={this.handleFirstQuestion.bind(this)}> Start! </button>
<p> </p>
{this.state.handleFirstQuestion
? <FirstQuestionBox />
: null
}
</div>
</center>
</div>
)
}
}
The first thing is that you are binding the function twice, one in constructor and one in onClick event handler.
Second pass props component in constructor and super
constructor(props) {
super(props);
this.state = {
handleFirstQuestion: false,
};
this.handleFirstQuestion = this.handleFirstQuestion.bind(this)
}
return (
<div>
<center>
<div>
<button onClick={this.handleFirstQuestion}> Start! </button>
<p> </p>
{this.state.handleFirstQuestion
? <FirstQuestionBox />
: null
}
</div>
</center>
</div>
)
I don't think you are exporting the class component
write this after the class component
export default ChoiceBar;
you should probably use aero functions and useState hook instead of the class component
for example:
const ChoiceBar = () => {
const [handleFirstQuestion, setHandleFirstQuestion] = useState(false)
}
I have Simple Add and Delete to my List sample ..
I made two child components
Lead Form Component ( Which Add New Leads to the List )
Lead List Component ( Which Simply render Leads List also have delete button which trigger delete action by passing ID back to parent )
In parent , the.state.leads holds all the leads
on Form Submit .. it adds to the.state.leads and LEAD LIST CHILD Components
successfully Re-Renders with new added lead
but on deleting list in the LEAD LIST , The lead list not re renders
Image ; Dev Tool Debug in the browser -React Console screenshot ..
MY LeadList Component
.........................................................
class LeadList extends React.Component {
constructor(props) {
super(props);
this.state = {
leads: this.props.avlList
};
this.handelDeleteLead = this.handelDeleteLead.bind(this);
}
handelDeleteLead(e) {
e.preventDefault();
this.props.DeleteLead(e.target.id);
}
render() {
console.log(this.state.leads);
return (
<div>
<ul>
{this.state.leads.map(item => (
<li key={item.id}>
{item.name} - {item.mobile} -{item.active ? "Active" : "Inactive"}
-
<div
id={item.id}
onClick={this.handelDeleteLead}
cursor="pointer"
>
X
</div>
</li>
))}
</ul>
</div>
);
}
}
......
My APP.js Parent Componnet
....................................
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
leads: [{ id: 1, name: "Panindra", mobile: "88842555542", active: true }]
};
this.handleAddToLeads = this.handleAddToLeads.bind(this);
this.handleRemoveLeads = this.handleRemoveLeads.bind(this);
}
handleAddToLeads(lead) {
let newleadsTemp = this.state.leads;
lead.id = Math.random() * Math.random();
newleadsTemp.push(lead);
// assign a name of list to item list
let newLeads = newleadsTemp;
this.setState({
leads: newLeads
});
}
handleRemoveLeads(lead_id) {
console.log(" Leads list before fitler ..." + this.state.leads);
let newFitleredLeads = remove(this.state.leads, lead_id);
this.setState({
leads: newFitleredLeads
});
console.log(" Leads list after fitler ..." + this.state.leads);
}
render() {
return (
<div className="App">
<h1> My First Redux</h1>
<hr />
<div className="leadList">
<LeadList
avlList={this.state.leads}
DeleteLead={this.handleRemoveLeads}
/>
</div>
<div className="leadForm">
<LeadForm NewLead={this.handleAddToLeads} />
</div>
</div>
);
}
}
.....
I think the problem is that you use state in LeadList component. Try to remove state from LeadList component. You don't need to manage multiple state's (this is important).
class LeadList extends React.Component {
render() {
return (
<div>
<ul>
{this.props.avlList.map(item => (
<li key={item.id}>
{item.name} - {item.mobile} -{item.active ? "Active" : "Inactive"}
-
<div
id={item.id}
onClick={() => this.props.DeleteLead(item.id)}
cursor="pointer"
>
X
</div>
</li>
))}
</ul>
</div>
);
}
}
And fix handleRemoveLeads function in the parent (App) component.
handleRemoveLeads(lead_id) {
console.log(" Leads list before fitler ..." + this.state.leads);
// THIS IS NOT WORKING
//let newFitleredLeads = remove(this.state.leads, lead_id);
// BETTER SOLUTION
let newFitleredLeads = this.state.leads.filter(item => item.id !== lead_id);
this.setState({
leads: newFitleredLeads
});
console.log(" Leads list after fitler ..." + this.state.leads);
}
This should work fine.
Working example (without form): https://codesandbox.io/s/charming-kowalevski-rj5nj
I have a page which presents a list of projects, when a user clicks on that particular project a view component is called in the render().
Parent :
constructor(props) {
super(props);
this.state = {
showData : [],
view : false,
projectId: ''
};
this.buttonHandler = this.buttonHandler.bind(this);
this.back = this.back.bind(this);
};
// change view state to true to render diff component
buttonHandler(){
this.setState({view:true})
};
back(){
this.setState({view:true})
}
render(){
let compA = (
<Paper>
<List>
<Subheader >New
Projects</Subheader>
{this.state.showData.map(item =>
<div key={item.title}>
<ListItem onClick={()=>
this.buttonHandler()} leftAvatar=
{<Avatar icon={<Wallpaper />} />} primaryText=
"test" secondaryText="test" />
<Divider inset={true} />
</div>
)}
</List>
</Paper>
);
let compB = (
<ReviewProject
back={this.back}/>
);
return(
<div>
{this.state.view?compB:compA}
</div>
);
}
child comp B :
constructor(props) {
super(props);
this.state = {
//some code
};
}
//calls function back from parent which sets state the "view" to false
dismiss() {
this.props.back();
};
When the dismiss() function is called at the child to render back the list component compA, a warning pops out:
Warning: setState(...): Can only update a mounted or mounting component. This usually means you called setState() on an unmounted component. This is a no-op. Please check the code for the compB component.
Is there a way to solve this? and what is a good practice to navigate from one component to the other back and forth
Because actually on first render, it's kind of if compA is rendered, then compB is missing, therefore when the react tries to re-render, it cannot find the missing element.
==> SOLUTION:
render(){
let returnedComp = (
<Paper>
<List>
<Subheader >New Projects</Subheader>
{this.state.showData.map(item =>
<div key={item.title}>
<ListItem onClick={()=>
this.buttonHandler()} leftAvatar=
{<Avatar icon={<Wallpaper />} />} primaryText=
"test" secondaryText="test" />
<Divider inset={true} />
</div>
)}
</List>
</Paper>
);
if (this.state.view) {
returnedComp = (
<ReviewProject back={this.back}/>
);
}
return(
<div>
{returnedComp}
</div>
);
}
You can try using ref also before setting your state as defined below
function(){
if (this.refs.ref)
this.setState({view: true});
}
render() {
return (
<div ref="ref">{this.state.view}</div>
);
}
The solution I found is:
To create a third component called viewProject component to handle the switching between A and B.
class A extends React.Component {
constructor(props) {
super(props)
}
render() {
return (
<div>
This is component A
<button onClick={this.props.onGoBClick}>Go to B</button>
</div>
)
}
}
class B extends React.Component {
constructor(props) {
super(props)
}
render() {
return (
<div>
This is component B
<button onClick={this.props.onGoAClick}>Go to A</button>
</div>
)
}
}
class ViewContainer extends React.Component {
constructor(props) {
super(props);
this.state = {
view: false,
}
this.goToA = this.goToA.bind(this);
this.goToB = this.goToB.bind(this);
}
goToA() {
this.setState({view: false})
}
goToB() {
this.setState({view: true})
}
render() {
return (
this.state.view ?
<B onGoAClick={this.goToA}/>
:
<A onGoBClick={this.goToB}/>
)
}
}
ReactDOM.render(<ViewContainer />, document.getElementById('app'))
Trying to wrap my head around passing events from Container to Presentational components.
As I understand it I attach the event to the Presentational/view level component. But I pass the functionality from the Container component to the Presentational component as a prop. I'm making sure to bind my function to this, but am getting a "Cannot read property 'onMouseEnterHandler' of undefined" error.
How am I not properly passing or binding this function?
class FeaturesContainer extends React.Component {
constructor(props) {
super(props);
this.state = {
hovered: false
}
this.onMouseEnterHandler = this.onMouseEnterHandler.bind(this);
}
onMouseEnterHandler() {
this.setState({
hovered: true
})
console.log('mouse enter, ' + this.state.hovered);
}
render() {
return(
<div className="page features" >
<ul className="features-list">
{data.features.map(function(obj, i) {
return (
<li key={i}>
<Feature {...obj} onMouseEnterHandler={this.onMouseEnterHandler} />
</li>
)
})}
</ul>
</div>
)
}
}
class Feature extends React.Component {
render() {
var bgImg = {
backgroundImage: 'url(' + this.props.img + ')'
};
return (
<div className="feature-container" onMouseEnter={this.props.onMouseEnterHandler}>
<div style={bgImg} className="feature-img"></div>
<div className="feature">
<h4 className="feature-issue">Issue {this.props.issue}</h4>
<h1 className="feature-title">{this.props.title}</h1>
<h4 className="feature-copy">{this.props.copy}</h4>
</div>
</div>
)
}
}
this inside the .map callback is undefined. That's why you get an error when trying to access this.onMouseEnterHandler.
Either use an arrow function:
data.features.map((obj, i) => { ... })
or pass this as second argument to .map:
data.features.map(function(obj, i) { ... }, this)