Get component's prop in its event handler in React - javascript

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

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)

Handle click on pagination link

I have this code and I want that after click li console.log display number of page. So I count my pages in pages const. I tryed do that on two way.
First:
const handlePageClick = (i) => {
console.log(i);
}
const Pagination = ({pages}) => {
let list = []
for(let i = 1; i <= pages; i++){
list.push(<li key={i} onClick={handlePageClick(i)}>{i}</li>)
}
return list;
}
return (
<React.Fragment>
<div className="row">
<ul>
<Pagination pages={pages} />
</ul>
</div>
</React.Fragment>
);
It run handlePageClick method after run the app, not after click.
Second method I tryed:
const linkRef = useRef(null);
const handlePageClick = (i) => {
console.log(linkRef.current.innerText);
}
const Pagination = ({pages}) => {
let list = []
for(let i = 1; i <= pages; i++){
list.push(<li key={i} ref={linkRef} onClick={handlePageClick}>{i}</li>)
}
return list;
}
It display the last result all the times. How can I solve my problem?
You have to pass a function to your onClick handler, so you can call handlePageClick in that function, and pass your current value of i like this:
const Pagination = ({pages}) => {
let list = []
for(let i = 1; i <= pages; i++){
list.push(<li key={i} ref={linkRef} onClick={() => handlePageClick(i)}>{i}</li>)
}
return list;
}
Notice how you're now passing () => handlePageClick(i) is now what you're passing to onClick. This is a function that gets executed when the click event happens, and it passes your current value of i
In your first attempt you need to use onClick={() => handlePageClick(i)}. This is because currently it is invoked on the app render because you are invoking the method so you create an infinite loop where the app is rendered -> the onClick event is invoked which causes a another render. Using an arrow function means you pass a function which is why you invoke it.

React event-handler shows the same index value

have a simple React component that displayes a character and should call a handler when clicked, and supply a number. The component is called many times, thus displayed as a list. The funny thing is that when the handler is called, the supplied index is always the same, the last value of i+1. As if the reference of i was used, and not the value.
I know there is a javascript map function, but shouldn't this approach work too?
const charComp = (props) => {
return (
<div onClick={props.clicked}>
<p>{props.theChar}</p>
</div>
);
deleteHandler = (index) => {
alert(index);
}
render() {
var charList = []; // will later be included in the output
var txt = "some text";
for (var i=0; i< txt.length; i++)
{
var comp =
<CharComponent
theChar = {txt[i]}
clicked = {() => this.deleteHandler(i)}/>;
charList.push(comp);
}
Because by the time you click on a letter, i is already 9 and it will remain 9 since the information is not held anywhere.
If you want to keep track of the index you should pass it to the child component CharComponent and then pass it back to the father component when clicked.
const CharComponent = (props) => {
const clickHandler = () => {
props.clicked(props.index);
}
return (
<div onClick={clickHandler}>
<p>{props.theChar}</p>
</div>
);
};
var comp = (
<CharComponent theChar={txt[i]} index={i} clicked={(index) => deleteHandler(index)} />
);
A little codesandbox for ya

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>);
}

Passing function to child in React

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>;
}

Categories

Resources