Passing function to child in React - javascript

I'd like to pass functions to the child using props, and create several components that can be clicked on
parent class:
handleClick(i){
alert(i);
}
render(){
var items = [];
for (var i = 0; i < 7; i++) {
items.push(
<Item onClick={()=>this.handleClick(i)} />
);
}
return <ul>{items}</ul>;
}
child class:
render() {
return (
<li onClick={this.props.onClick}> some names </li>
);
}
But the result is different from what I expected.
I wanted the first element to alert(0), the second element toalert(1), and so on.
But instead, all elements shows 7 when I click on them. I guess that's because I'm always using the i after for-loop has finished.
I guess this is a problem about basic concepts of scopes or using closure or something, rather than a React problem. But still I can't find a right way to fix this problem.

It happens because of closure, since your are using var keyword for forLoop iterator, its scope will be the render function and the value passed to handleClick will always be the updated value of iterator. Use let keyword to solve closure issue
render(){
var items = [];
for (let i = 0; i < 7; i++) { // let keyword for iterator
items.push(
<Item onClick={()=>this.handleClick(i)} />
);
}
return <ul>{items}</ul>;
}
Even with var, you can solve the closure issue using anonymous function
render(){
var items = [];
for (var i = 0; i < 7; i++) {
(function(index){
items.push(
<Item onClick={()=>this.handleClick(i)} />
);
}.bind(this))(i)
}
return <ul>{items}</ul>;
}

Related

ReactJS for loop create buttons and set variable according to the integer in the loop

I am new to ReactJS, I am trying to create 10 buttons with for loops, and set a variable with the integer from the for loop, however the function setMqty(i) always returns 11 for all the buttons. What is the correct way for doing this?
var qtyButtons = [];
for(var i = 1; i < 11; i++) {
qtyButtons.push(<div
onClick={(btn) => {qtyBtnToggle(btn);setMqty(i); }}
>
{i}
</div>)
}
Thank you in advance.
The main issue here has to do with scoping. Let me reduce your example to a more minimal example:
var qtyCallbacks = [];
for (var i = 1; i < 11; i++) {
qtyCallbacks.push(() => console.log(i));
}
for (const callback of qtyCallbacks) {
callback();
}
What's going wrong here? Why is it only logging 11 and not 1-10?
The issue is that a variable defined with var are scoped to the nearest function or globally if not in a function. So what does this mean?
It means that each iteration in your for loop refers to the same i. This isn't an issue if you directly use i.
for (var i = 1; i < 11; i++) {
console.log(i); // logs 1-10
}
However it does become an issue if you refer to i in code that is executed at a later point. The reason the initial snippet only logs 11 is because all the callbacks are created, each one referring to the same i. So when you evoke each callback after the for-loop is complete, they will all refer to the current value (which is 11).
for (var i = 1; i < 11; i++) {
}
console.log(i); // logs 11
// so if we invoke the callbacks here they all log 11
So how do you solve this issue? The simplest solution is probably to replace var with let. let will create a new variable for each for-iteration. Meaning that each created callback will refer to their own i and not a shared i. This also means that you cannot access i outside of the for-loop.
var qtyCallbacks = [];
for (let i = 1; i < 11; i++) {
qtyCallbacks.push(() => console.log(i));
}
for (const callback of qtyCallbacks) {
callback();
}
console.log(i); // expected error, `i` is only available within the for-block
I think using a map function is the correct way for doing it.
Therefore, you must generate a range of numbers our any element to be able to perform a map function.
So if i was in your place i would have do it like:
const [mqty, setMqty] = useState(0);
// ...
const qtyButtons = Array(10).fill().map((_, i) => {
const value = parseInt(i);
return (
<button onClick={handleClick(value)}>
{value}
</button>
);
});
// ...
// The trick here is that function returning another function, beware of that
const handleClick = (i) => () => {
setMqty(i);
};
I am guessing setMqty is a state.Can you try
setMqty(currentValue=> currentValue+1)

Get component's prop in its event handler in React

There is a React component with a key prop and onClick event handler. I need to create some number of these components and give their key value as an argument for the click event handler. But if the argument is value variable it takes its final value after the iterations of for loop. For example, if I create three components, handleClick function gets value = 3 from every component, but it should be 1 from the first component, 2 from the second and so on.
createComponents(n) {
let value = 0;
let list = [];
for (let i = 0; i < n; i++) {
list.push(
<Component
key={value}
onClick={() => {
handleClick(value);
}}
/>
);
value += 1;
}
return list;
}
this within that arrow function will refer to the component rendering this, not the Component instance you're rendering.
Instead of value, you can use i. Since it's declared within the for statement with let, a *different i is created for each loop iteration (yes, really). So:
createComponents(n) {
let list = [];
for (let i = 0; i < n; i++) {
list.push(
<Component
key={i}
onClick={() => {
handleClick(i);
}}
/>
);
}
return list;
}
If for some reason value and i aren't the same as they are in your question, you can use a local constant (or variable) within the for block to capture value's value:
createComponents(n) {
let value = 0;
let list = [];
for (let i = 0; i < n; i++) {
const thisValue = value;
list.push(
<Component
key={thisValue}
onClick={() => {
handleClick(thisValue);
}}
/>
);
++value;
}
return list;
}
Since that's declared with const, it's local to the block scope created for each iteration (just like i). That would be true if you used let as well (but not var).
You have a scoping issue. handleClick() references value which at time of execution is n - 1 (upper limit of for loop). what you need is to make handleClick() reference a block level variable, like i or introduce another closure in handleClick().
createComponents(n) {
let value = 0; // do not use this, all handleclicks will reference last set value
let list = [];
for (let i = 0; i < n; i++) {
list.push(
<Component
key={i}
onClick={() => {
handleClick(i);
}}
/>
);
}
return list;
}
With above code, you will achieve same thing as value changes same as i.
You can read more about closures and lexical scoping

How to loop through and display buttons that have an onClick and pass a different variable to another function?

I have the following ReactJS code which I am using to render a number of buttons which will allow the user to navigate around the data returned from an API (a bunch of paginated images).
I have the multiple buttons displaying but they send i as 19 (the end value in the loop) to the handlePageClick() function.
How can I get the value of i to be passed to my handlePageClick() function?
handlePageClick( button ) {
console.log( button );
this.setState( prevState => {
return {
currentPage: button
}
}, () => {
this.loadMedia();
});
}
render() {
// figure out number of pages
const mediaButtonsNeeded = parseInt( this.state.totalMedia / this.state.perPage )
var mediaButtons = [];
for (var i = 0; i < mediaButtonsNeeded; i++) {
mediaButtons.push(<button onClick={() => this.handlePageClick(i)}>{i}</button>);
}
return (
<div>
<div>
<h1>Media test</h1>
{media}
{mediaButtons}
</div>
</div>
)
Since var is global scope variable declaration, your loop always rewrite i to next value.
Easiest solution is just replacing from var to let.
for (let i = 0; i < mediaButtonsNeeded; i++) {// your code...
This should work:
for (var i = 0; i < mediaButtonsNeeded; i++) {
mediaButtons.push(<button onClick={() => this.handlePageClick(i+"")}>{i}</button>);
}

Why does my for loop not work inside my class before render method in React?

I founds several answers here actually addressing this problem, which is why I found my general approach. But as most answers say, insert the code snippet:
var squares =[];
for(let i=1; i<10; i++){
squares.push(<div className="square" key={i}></div>);
}
inside the class before the render method. But it does not work. Then I put the same code outside off the class and it works but now I have no access to the state scope (if I am not mistaken). I am also confused, why my code editor wouldn't let me change var to let (inside class).
Any ideas?
Thanks.
most answers say insert the code snippet inside the class before the render method. But it does not work.
Those answers probably meant this:
someMethod() {
var squares =[];
for(let i=1; i<10; i++){
squares.push(<div className="square" key={i}></div>);
}
}
render() {
return (
// ...
)
}
Whereas you're probably doing this:
var squares =[];
for(let i=1; i<10; i++){
squares.push(<div className="square" key={i}></div>);
}
render() {
return (
// ...
)
}
Then I put the same code outside off the class and it works but now I
have no access to the state scope
Correct, that is how it should be.
I also suspect that you want to display this on the screen, in which case you should also return it:
someMethod() {
var squares =[];
for(let i=1; i<10; i++){
squares.push(<div className="square" key={i}></div>);
}
return squares;
}
render() {
return (
<div>
{this.someMethod()}
</div>
)
}
But as most answers say, insert the code snippet inside the class
before the render method.
No you would not insert the for loop directly within the class outside the render method, but directly inside it
render () {
var squares =[];
for(let i=1; i<10; i++){
squares.push(<div className="square" key={i}></div>);
}
return (
<div>{squares}</div>
)
}

Sliding accordion with pure JS and for loop

I've written a for loop, which should run through all accordions and their children, but I can't figure out why it's only working on the first object.
Fiddle Example
for (
var i = 0,
accordion = document.getElementsByClassName('accordion');
i < accordion.length;
i++
) {
var accordion_section = accordion[i].children[i],
accordion_key = accordion[i].children[i].children[0],
accordion_bellow = accordion[i].children[i].children[1];
function accordion_bellow_MarginTop( value ) {
accordion_bellow.style.marginTop = value + 'px';
}
accordion_bellow_MarginTop( -accordion_bellow.offsetHeight );
accordion_key.onclick = function() {
if ( accordion_section.getAttribute('class' ) == 'active' ) {
accordion_section.setAttribute('class', '');
accordion_bellow_MarginTop( -accordion_bellow.offsetHeight );
}
else {
accordion_section.setAttribute('class', 'active');
accordion_bellow_MarginTop( 0 );
}
return false;
}
}
There are a couple of issues at play here. As previous commenters noted, you are not properly looping over each of the sections within your accordion. After fixing that, you will also need to address the fact that your onClick handler will not work correctly.
The problem with looping over each section is that you are using improper variable scoping. What happens is only the last element you loop over will be affected by the click handler. This is because the variables "accordion_section" and "accordion_bellow" will always reference the last set of elements in your main for loop.
This is contrary to the expectation that they will be the unique element assigned during the loop. The reason for this is because the variables "accordion_section" and "accordion_bellow" are defined outside the scope of the onClick function. In order for your onClick to work, those variables need to be defined within a separate scope during each iteration of your for loop.
In order to do this, you can use an anonymous function like this:
for (var i = 0; i < sections.length; i++)
{
(function() {
var section = sections.item(i),
anchor = sections.item(i).children[0],
below = sections.item(i).children[1];
closeBelow(below, -below.offsetHeight);
anchor.onclick = function () {
if (section.getAttribute('class' ) == 'active' ) {
section.setAttribute('class', '');
closeBelow(below);
}
else {
section.setAttribute('class', 'active');
openBelow(below);
}
}
})();
}
In this solution, the variables "section", "anchor", and "below" are always specific to the elements you are looping over. By using a self-executing function, you ensure that each click handler only works with locally scoped variables.
Solution: http://jsfiddle.net/b0u916p4/4/
you need to make another loop inside first
this way you get all accordion and all sections of each
try this:
for (i = 0,
accordion = document.getElementsByClassName('accordion');
i < accordion.length;
i++) {
for (j = 0;
j <= accordion[i].children.length;
j++) {
//your code here
}
}

Categories

Resources