ReactJs event passed from Container to Presentational Components - reactjs

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)

Related

Handle multiple child component in React

I've tried to look everywhere and couldn't find anything related to my use case, probably I'm looking for the wrong terms.
I have a situation where I have a bar with 3 icons, I'm looking for set one icon "active" by changing the class of it.
The icon is a custom component which have the following code
export default class Icon extends Component {
state = {
selected : false,
}
setSelected = () => {
this.setState({
selected : true
})
}
setUnselected = () => {
this.setState({
selected : false
})
}
render() {
var classStatus = '';
if(this.state.selected)
classStatus = "selected-icon"
else
classStatus = "icon"
return <div className={classStatus} onClick={this.props.onClick}><FontAwesomeIcon icon={this.props.icon} /></div>
}
}
In my parent component I have the following code
export default class MainPage extends Component {
handleClick(element) {
console.log(element);
alert("Hello!");
}
render() {
return (
<div className="wrapper">
<div className="page-header">
<span className="menu-voice">File</span>
<span className="menu-voice">Modifica</span>
<span className="menu-voice">Selezione</span>
</div>
<div className="page-main">
<span className="icon-section">
<div className="top-icon">
<Icon icon={faFileCode} onClick={() => this.handleClick(this)} />
<Icon icon={faCodeBranch} onClick={() => this.handleClick(this)} />
<Icon icon={faMagnifyingGlass} onClick={() => this.handleClick(this)} />
</div>
</span>
<span className="files-section">Files</span>
<span className="editor-section"></span>
</div>
<div className="page-footer">
Footer
</div>
</div>
);
}
}
What I'm trying to achieve is that when one of the Icon child component get clicked it will set the selected state to true manage by the parent component, in the same time while one of them is true I would like that the parent would set to false the other twos.
I've tried to use the useRef function but it doesn't look as a best practise.
Which is the correct way to do it? Sending also this to the handleClick function it just return the MainPage class instead of the child. Any suggestion at least where I should watch?
Thanks in advance
I suggest not storing the state in the icon, since it doesn't know what else you're using it for. Simply have the icon component take it's 'selected' status from props. e.g.
export default class Icon extends Component {
render() {
var classStatus = '';
if(this.props.selected)
classStatus = "selected-icon"
else
classStatus = "icon"
return (
<div className={classStatus} onClick={this.props.onClick}>.
<FontAwesomeIcon icon={this.props.icon} />
</div>
);
}
};
Then you can just manage the state in the parent where it should be:
export default class MainPage extends Component {
constructor(props) {
super(props);
this.state = { selectedOption : '' };
}
handleSelectOption(newValue) {
this.setState({ selectedOption: newValue });
}
isSelected(value) {
return value === this.state.selectedOption;
}
render() {
return (
<div className="wrapper">
{ /* etc... */ }
<div className="page-main">
<span className="icon-section">
<div className="top-icon">
<Icon
icon={faFileCode}
onClick={() => this.handleSelectOption("File")}
selected={isSelected("File")}
/>
<Icon
icon={faCodeBranch}
onClick={() => this.handleSelectOption("Modifica")}
selected={isSelected("Modifica")}
/>
{ /* etc... */ }
</div>
</span>
</div>
{ /* etc... */ }
</div>
);
}
};
You should define a constructor in your class component:
constructor(props) {
super(props);
this.state = { selected : false };
}
You also have to call a function which modify the state when you click on the Icon. onClick={this.props.onClick} doesn't change the state

How to pass an user-created array to another component?

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;

Child Not Re-rendering on List Item Deletion in Parent but Adding to the List Item Triggering Re-render

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

Reactjs - how to append a font-awesome icon to ref

I'm trying to use appendChild to add a fontawesome icon to a ref in react. I get the error:
Failed to execute 'appendChild' on 'Node': parameter 1 is not of type 'Node'.
Using append instead of appendChild can only add text.
class Example extends React.Component {
handle = (e) => {
e.target.appendChild('<i className="fas fa-minus-circle"/>')
}
render() {
return (
<div className="container">
<button onClick={this.handle}>CLICK HERE</button>
</div>
)
}
}
Consider changing your approach a little. You can achieve the same result you're expecting with the code below, and it's a little more common to use state and conditional JSX in React:
class Example extends React.Component {
state = { images: [] }
handle = () => {
this.setState({ images: [...images, 'fas fa-minus-circle'] });
}
render() {
return (
<div className="container">
<button onClick={this.handle}>CLICK HERE</button>
{this.state.images.map((image, index) => {
<i key={index} className={image} />
})}
</div>
);
}
}
You can create ref and attach it to the element you want to add the icon.
class Example extends React.Component{
constructor(props){
super(props);
this.iconRef= React.createRef();
}
handle = (e) => {
e.prenventDefault();
this.iconRef.current.appendChild('<i className="fas fa-minus-circle"/>')
}
render() {
return (
<div className="container">
<button ref={this.iconRef} onClick={this.handle}>CLICK HERE</button>
</div>
)
}
}

How to pass a javascript-generated DOM element to react render function

I am a newbie to react. I'm trying to create a component named StationBrowserForHome (TSX). In the render() function, I want to render a DOM element looks like this:
<div class = "big-group">
<div class = "small-group>
<div class="item"></item>
<div class="item"></item>
...
<div class="item"></item>
</div>
<div class = "small-group>
<div class="item"></item>
<div class="item"></item>
...
<div class="item"></item>
</div>
...
</div>
So I tried to create that DOM tree in componentWillMount(), set it to state variable "divItems", and in render(), I retrieve it
export class StationBrowserForHome extends Component <{}, any> {
componentWillMount (){
var divItems = document.createElement("div");
var divSeven = document.createElement("div");
for (var i = 0; i < 7; i++) {
var singleItem = document.createElement('<div className="station-item"><div className="row"><StationBrowserItemInfo/></div><div className="row station-name">Station {i}</div></div>');
divSeven.appendChild(singleItem);
console.log("singleItem: "+singleItem);
console.log("divSeven: "+divSeven);
}
divItems.appendChild(divSeven);
this.setState({divItems:divItems});
}
render() {
return (
<div className="container-fluid text-center browser">
<div className="cover-div">
<ArrowButton/>
<div className="text-center list-station" id="station-browser">
{ this.state.divItems }
</div>
</div>
</div>
);
}
}
And I got this error message:
VM19339 invariant.js:42 Uncaught Error: Objects are not valid as a
React child (found: [object HTMLDivElement]). If you meant to render a
collection of children, use an array instead
Any help would be appreciated!
React is great because you can break things down into reusable components. This code kind of goes against the whole meanings of react.
Everything in componentWillMount should be inside a component with props numberOfItems for example and then based on that you will render what it seems to me would be a station item.
export class StationList extends React.Component<{stations: IStation[]}, {}> {
render() {
return (
<div className='stations'>
{this.getStationItems}
</div>
);
}
private getStationItems() {
const itemsDiv = this.props.stations.map((station, index) => {
return (
<div className='station'>
//........
// other stuff here
</div>
)
});
return itemsDiv;
}
}
Then back in the StationBrowserForHome component we just:
const stations: IStation[] = [
{
id: 'station1',
purpose: 'purpose1'
},
{
id: 'station2',
purpose: 'purpose2'
}
];
return (
<div className="container-fluid text-center browser">
<div className="cover-div">
<ArrowButton/>
<div className="text-center list-station" id="station-browser">
<StationList stations={stations} />
</div>
</div>
</div>
);
The code above is just a generic template not in front of an IDE but thats the basic gist.
why you use this way , you can easily create an element with JSX like this
componentWillMount() {
let ele = <div> </div>;
}
and if you want loop with element or any work on that you can use map function (method for looping in javascript like foreach) like this
componentWillMount() {
this.state.count.map( (e) => ( ... ) )
}
in above example e parameter , refer on your object for example
constructor(props) {
super(props);
this.state = {
data:[
{
id:1,
name:'hamid'
},
{
id:2,
name:'mat'
},
{
id:3,
name:'sara'
}
]
}
this.state.data.map( (item) => (
<li key={item.id} > {item.name} </li>
));
you have this in your DOM
<li>hamid</li>
<li>mat</li>
<li>sara</li>
and you must set key attribute in top level root element inside map function , and this must be uniq for each item
Its better to create on functional component and passed the required data for its rendering.
const MyComp = ({counter}) => {
return (
<div>{(" ".repeat(7).split("")).map((value, index) => (
<div class="big-group">
<div class="small-group">{index+1}</div> //for example
</div>
))
}</div>
);
}
Inside the render, passed the state data to it.data
render(){
return(<div>
....
<MyComp counter = {7} />
</div>);
}

Resources