React - Pass props through function call - javascript

I have a component which renders the following:
render() {
return (
<div className = "events card" >
<div key = {each.id}>
<ul>
<li className = "card-text">Venue Name: {each.name} </li>
<li className = "card-text">Country: {each.country} </li>
<li className = "card-text">City: {each.city} </li>
<li className = "card-text">Date: {each.date}</li>
</ul>
</div>
<a onClick = {this.addToFavourites.bind(this)}>
<Button text = "Add to Favourites"/>
</a>
</div
);
}
addToFavourites() {
... do something
}
When I call addToFavourites, I want to pass the name, country, city and date to this function as it is going through an array of events so I don't know how else the function will do what it's supposed to do with the selected data.

THis is what you can do:
<a onClick = {() => this.addToFavourites(each)}><Button text = "Add to Favourites"/></a>
This will ensure that the object will be passed to the addToFavourites method.
And from there, you can access the properties from each in the method itself.
addToFavourites(each) {
const { name, country, city, date } = each;
// do the rest
}

Answer by #wentjun is very much correct. I just want to propose an alternative solution.
You can pass the arguments you want to bind.
But you should also know both these practices are not advisable. Read more here Why shouldn't JSX props use arrow functions or bind?
render() {
return (
<div className = "events card" >
<div key = {each.id}>
<ul>
<li className = "card-text">Venue Name: {each.name} </li>
<li className = "card-text">Country: {each.country} </li>
<li className = "card-text">City: {each.city} </li>
<li className = "card-text">Date: {each.date}</li>
</ul>
</div>
<a onClick = {this.addToFavourites.bind(this, each)}>
<Button text = "Add to Favourites"/>
</a>
</div
);
}
addToFavourites(each) {
const { name, country, city, date } = each;
// do the rest
}

Related

Navbar TypeError: Cannot read property 'classList' of null

I am trying to implement a navbar to html which has the effect dynamically switch pages instead of changing links via href. The trick I'm using to accomplish this is by adding a classList of is-active to the div elements in the section tag.
Here is an example code of the generated HTML :
navBar.js
const navbarItem = [
{
navitem : "About",
link: 1
},
{
navitem : "Legal",
link: 2
},
{
navitem : "Contact",
link: 3
}
];
window.onload = function navLoad() {
const navbar = document.getElementById('navbar');
navbar.innerHTML =
`
<div class="toggle">
<i class="fa fa-bars menu" aria-hidden="true"></i>
</div>
<ul class="nav-list">
<li class="tab is-active">
<a onclick="renderNav()">Home</a>
</li>
${navbarItem.map(loadNavitems).join(" ")}
</ul>
`
}
function loadNavitems(navItems) {
return `
<li class="tab">
<a data-switcher data-id="${navItems.link}" onclick="renderNav(); navSwap();">${navItems.navitem}</a>
</li>
`
}
function renderNav(){
const pages = document.getElementById('main');
document.getElementById('alphabetButtons').style.display = "block";
pages.innerHTML =
`
<section class="pages">
${navbarItem.map(item => `
<div class="page" data-page="${item.link}">
<h1>${item.navitem}</h1>
</div>
`).join('')}
</section>
`
};
And here is the code which takes care of the page switching:
navSwitcher.js
function navSwap() {
const tab_switchers = document.querySelectorAll('[data-switcher]');
for (let input of tab_switchers) {
const page_id = input.dataset.switcher;
console.log(page_id);
input.addEventListener('click', function () {
if(document.querySelector('.nav-list .tab.is-active')){
document.querySelector('.nav-list .tab.is-active').classList.remove('is-active');
console.log('removed');
}
if(input.parentNode.classList.contains('tab')){
input.parentNode.classList.add('is-active');
}
//SwitchNav(page_id);
});
}
}
function SwitchNav(page_id) {
const currentPage = document.querySelector('.pages .page');
const next_page = document.querySelector(`.pages .page[data-page="${page_id}"]`);
console.log(next_page);
if(document.querySelector('.pages .page.is-active')){
document.querySelector('.pages .page.is-active').classList.remove('is-active');
}
next_page.classList.add('is-active');
}
Update : The issue is that causes the error comes when attempting to place [data-page="${page_id}"] after .page inside the querySelector. When console logging, [data-page="${page_id}"] will return null. This is weird because data-page is named correctly in the renderNav function which holds the div of class page.
Hence my hypothesis now is that the issue comes from [data-page="${page_id}"] in the SwitchNav(page_id) function. My question is why is this occuring if everything is named correctly?
Fixes tried:
Attempted to change the for/of loop to a regular for loop inside the navSwap function.
Inside the navSwap function, const page_id = input.dataset.tab; was changed to const page_id = input.dataset.switcher; which now returns 3 items.
So after some digging around, in the end it was actually const page_id = tab_switcher.dataset.tab; which was returning undefined, hence causing the error to happen. In reality it was returning id so I changed it to tab_switcher.dataset.id;.
Here is the final attempt at the code (i placed everything in one single file):
navBar.js:
const navbarItem = [
{
navitem : "About",
link: 1
},
{
navitem : "Legal",
link: 2
},
{
navitem : "Contact",
link: 3
}
];
window.onload = function navLoad() {
const navbar = document.getElementById('navbar');
navbar.innerHTML =
`
<div class="toggle">
<i class="fa fa-bars menu" aria-hidden="true"></i>
</div>
<ul class="nav-list">
<li class="tab is-active">
<a onclick="renderNav()">Home</a>
</li>
${navbarItem.map(loadNavitems).join(" ")}
</ul>
`
}
function loadNavitems(navItems) {
return `
<li class="tab">
<a data-switcher data-id="${navItems.link}" onclick="renderNav(); navSwap();">${navItems.navitem}</a>
</li>
`
}
function renderNav(){
const pages = document.getElementById('main');
document.getElementById('alphabetButtons').style.display = "block";
pages.innerHTML =
`
<section class="pages">
${navbarItem.map(item => `
<div class="page" data-page="${item.link}">
<h1>${item.navitem}</h1>
</div>
`).join('')}
</section>
`
};
function navSwap() {
const tab_switchers = document.querySelectorAll('[data-switcher]');
for (let i = 0; i < tab_switchers.length; i++) {
const tab_switcher = tab_switchers[i];
const page_id = tab_switcher.dataset.id;
tab_switcher.addEventListener('click', function(){
document.querySelector('.nav-list .tab.is-active').classList.remove('is-active');
tab_switcher.parentNode.classList.add('is-active');
SwitchNav(page_id);
});
}
}
function SwitchNav(page_id) {
const currentPage = document.querySelector('.pages .page');
const next_page = document.querySelector(`.pages .page[data-page="${page_id}"]`);
console.log(next_page);
if(document.querySelector('.pages .page.is-active')){
currentPage.classList.remove('is-active');
}
if(document.querySelector('.pages .page')){
next_page.classList.add('is-active');
}
}
Although the code generates the intended results, I have to click twice on the tag to fire it's onclick event. If anybody knows why this occurs it would be of much help to know in the comments.
Also let me know if this is a viable fix. Thanks again :)

How can I loop in a map function in React?

I'm currently mapping through an array of products in the render method returning list elements for each object, I want to create an html select tag for the quantity of products available. Only way I can think of doing this is by looping out an option for every quantity, but this seems to throw a syntax error when using it inside the map function.
I can't seem to find any questions regarding this, the whole loop inside of map function already returning jsx.
render() {
const products = this.props.products.map((product, id) =>
<li key={id} className='products-list-container' >
<div className='product-inner-div-wrapper'>
<div className='product-title-container'>
<p>{product.name}</p>
</div>
<div className='product-price-container'>
<p>{product.price.formatted_with_code}</p>
</div>
// THIS IS WHERE I TRY TO LOOP AND CREATE AN OPTION
// FOR EVERY QUANTITY
<select>
{
for (var i = 0; i < products.quantity; i++) {
return <option value={i}>{i}</option>
}
}
</select>
<div className='product-add-item-container'>
{ product.is.sold_out ? <p>SOLD OUT</p> :
<p onClick={() => this.addItem(product.id)}>add to cart</p>
}
</div>
<div className='products-image-container'>
<img src={product.media.source} />
</div>
</div>
</li>
);
return(
<div className='products-list-container'>
<ul className='products-list-ul'>
{products}
</ul>
</div>
)
}
Instead of creating a loop, you can use a map here as well, with some array trickery:
{new Array(product.quantity).fill().map((_, i) => <option>{i}</option>)}
It's not very pretty - but you can pull this out into its own function, and name it descriptively.

Error passing function onClick in react

I have two files. A list Component and a Single Item Component. In my app, the user can select multiples items. Then I create an state element in "list" "items" and my idea is that when the user make click on the item button, the list element notify to List Component and save the item in Items array from "list".
I have the next code
List.jsx:
registrarItems(data,item){
console.log(data,"aqui 1 con",item);
let items = this.state.itemsAgregados.slice();
if(!items.indexOf(data.id_producto)){
console.log("no se encuentra");
items.push(id);
this.setState({
'itemsAgregados':items
});
}else{
console.log("ya existe");
item.removerSeleccion();
}
console.log("registrando items",id);
}
render() {
return (
<div className="content-app">
<Navbar data={this.menu}/>
<div className="container lista-productos">
{
this.state.productos.map((producto, index) => {
return (
<Item data={producto}
registro = {this.registrarItems}
key={producto.id_producto}/>
);
})
}
</div>
</div>
);
}
And Item.jsx:
render() {
let props = this.props;
let img = JSON.parse(props.data.imagen);
let imgPath = Rutas.apiStatic + 'img/productos/' + props.data.id_producto + '/' + img.sm;
props.data.items = this;
return (
<div className="row item-listado">
<div className="col-xs-3">
<img src={imgPath} className="img-circle img-item"/>
</div>
<div className="col-xs-7">
<Link to={Rutas.producto + props.data.identificador}>
<h3 className="titulo">{props.data.titulo}</h3>
<span className="price">$ {props.data.precio}</span>
</Link>
</div>
<div className="col-xs-2 text-right">
<ul className="list-unstyled list-acciones">
<li>
<a href="#" onClick={()=>props.registro(props.data,this)} className={this.state.classAdd}>
<i className="fa fa-plus"></i>
</a>
</li>
</ul>
</div>
</div>
)
}
As you can see, I pass the "registrarItems" method as a param to Item and there, i add this as onClick event in the tag from item. But I need pass the "data" and the own "item" element to the onclick function. The first, for save the element clicked in the Items array, or remove it (if it already exists) because the button may have a toggle function. But in my "console.log" both params passed on the onClick method with the arrow function shows as "undefined".
I saw some examples and i don't get my error. can anybody helpme? thanks.
The final solve for this was simple. I resolved it with something similar as Free-soul said on his comment.
First, I passed the List Component as a param to item. Below my code in List's render method:
{
this.state.productos.map((producto, index) => {
this.items[producto.id_producto] = producto;
return (
<Item data={producto}
parent = {this}
key={producto.id_producto}/>
);
})
}
Then I get the parent param in componentDidMount method and later I call the validarItem function directly from the List method and I pass the params that i need.
Here my Item code:
onClickPlus(id,data) {
//{Rutas.listas + 'productos/' + props.data.id_producto //Url para pasar uno solo
this.setState({
classAdd:'selected'
})
if(!this.state.parent.validarItem(this.state.data)){
this.removerSeleccion()
}
if(this.state.seleccionMultiple){
}
}
removerSeleccion(){
this.setState({classAdd:'normal'})
}
componentDidMount(){
this.setState({
parent: this.props.parent,
data : this.props.data
})
}
render() {
return (
// more code
<a href="#" onClick={() => this.onClickPlus(parent)} className={this.state.classAdd}>
<i className="fa fa-plus"></i>
</a>
//more render code...
)
}
I don't know if this is the best practice, but works for me.

ReactJs: target.key undefined after method is reached from li rendered by map function

I'm currently trying to coding a react app that would do the following:
- Create a list of questions from an array using a map function.
- Making each list element clickable using a onClick prop
- The linked onClick method changes the state in another file with my 'qsChange' prop.
I had a hard time making my list clickable and finally managed following this question: React: trying to add an onClick to a li tag but the click handler function is undefined
However, now I cannot make it so that my variable 'choice' returns a defined value. I would want var choice to be equal to "A ?", "B ?" or "C ?" depending on which I click.
Here's my code:
var questions = ["A ?", "B ?", "C ?"];
var Questions = React.createClass({
handleClick: function() {
var visibility;
if(this.props.visibility) {
document.getElementById('second').style.display = 'none';
visibility = false;
this.props.onChange(visibility);
} else {
document.getElementById('second').style.display = 'block';
visibility = true;
this.props.onChange(visibility);
}
},
/* Here is where my problem lies */
onItemClick: function(e){
var choice = e.target.key;
this.props.qsChange(choice);
alert(choice);
},
render: function() {
return (
<div className="bigqs">
<div id="first" className="small" style={firstStyle}>
<h1>Question :</h1>
<button style={btnStyle} onClick={this.handleClick}>
<img id="arrow" src="../../img/arrow.png" />
</button>
<h3 id="selectedQuestion">{this.props.selected}</h3>
</div>
<div id="second" className="small" style={{display: 'none'}}>
<h4>
<ul>
{questions.map(function(qs, i) {return <li key={qs[i]} onClick={this.onItemClick}>{qs}</li>;}, this)}
</ul>
</h4>
</div>
</div>
);
}
});
I am still a newbie, so please be indulgent ;-)
I hope I was clear enough.
Ps: I have also tried this guide but it didn't work for me: http://derpturkey.com/react-pass-value-with-onclick/
Instead of grabbing the question from target, you can pass question through to your handler. Also, since inside map qs is a string, qs[i] will be getting the character in the string from that index. You just need to make sure your key is unique.
onItemClick: function(choice) {
this.props.qsChange(choice)
alert(choice)
},
render() {
return (
<div>
...
{questions.map(qs =>
<li key={qs} onClick={() => this.onItemClick(qs)}>{qs}</li>
)}
...
</div>
)
}
In fact, your intermediate function isn't doing much, you can just call your props function inside render:
render() {
return (
<div>
...
{questions.map(qs =>
<li key={qs} onClick={() => this.props.qsChange(qs)}>{qs}</li>
)}
...
</div>
)
}

Unique "key" prop in React when returning list with multiple children

I have to render multiple lists in a react component. I create my lists in the following manner:
var jobResources = null;
if (this.state.jobResources) {
jobResources = _.map(this.state.jobResources, function(jobResource, i) {
return <ul><li key={i}>{jobResource.resource.name}</li><li key={Math.floor(Math.random() * (1000 - 100)) + 100}>{translations.resourceType[jobResource.resource.resourceType.name]}</li></ul>
})
}
When it comes to rendering, I render the list thus:
<div>
<h2>Who is Working on the Project?</h2>
{this.state.jobResources ? jobResources : null}
</div>
The resulting render is as follows:
<ul data-reactid=".0.2.0.2.1:0">
<li data-reactid=".0.2.0.2.1:0.$0">Mark Smith</li>
<li data-reactid=".0.2.0.2.1:0.$270">Hair & MakeUp</li>
</ul>
<ul data-reactid=".0.2.0.2.1:1">
<li data-reactid=".0.2.0.2.1:1.$1">John Doe</li>
<li data-reactid=".0.2.0.2.1:1.$377">Photographer</li>
</ul>
As far as I see it, the keys are unique. However, react gives me the following warning:
Warning: Each child in an array or iterator should have a unique "key"
prop. Check the render method of JobDetailPage. See
http://facebook.github.io/react/docs/multiple-components.html#dynamic-children
for more information.
Could anyone tell me what is the mistake that I am making and what would be the correct way of going about this?
Never mind, figured it out. Apparently, the list element should have a key as well. The correct way of creating the list therefore would be:
var jobResources = null;
if (this.state.jobResources) {
jobResources = _.map(this.state.jobResources, function(jobResource) {
return <ul key={jobResource.id}><li key={jobResource.resource.name}>{jobResource.resource.name}</li><li key={jobResource.resource.resourceType.name}>{translations.resourceType[jobResource.resource.resourceType.name]}</li></ul>
})
}
I usually do this
if (this.state.jobResources) {
jobResources = _.map(this.state.jobResources, function(jobResource, i) {
return (
<ul>
<li key={i.toString()}>{jobResource.resource.name}</li>
<li key={`key${i}`}>
{
translations.resourceType[
jobResource.resource.resourceType.name
]
}
</li>
</ul>
);
});
}
This utility function from lodash/underscore solves it easily:
key={_.uniqueId()}

Categories