Variable in event of react list component - javascript

Here is my code:
export default function App() {
const array = ["item1", "item2", "item3"];
let progress = "";
const list = array.map((item) => {
progress += `/${item}`;
return (
<input
key={item}
type="button"
value={item}
onClick={() => {
console.log(progress);
}}
/>
);
});
return <div className="App">{list}</div>;
}
Also, you can try it on sandbox
When I click item1 button on the page. Why the console log /item1/item2/item3?
The expected behavior is that:
When item1 button is clicked. Console log /item1;
When item2 button is clicked. Console log /item1/item2;
...
Thanks.

When you click the button, the arrow function you provided will fire. This means that the function will read the variable only when the arrow function is being called. And since the progress variable isn't being created inside the mapping or arrow function, but rather outside it, the last mapped input element will set it to item1item2item3, meaning it will be the same when you click each of the inputs.
To fix this, you could assign a local variable to be equal to progress inside the map method, like so:
export default function App() {
const array = ["item1", "item2", "item3"];
let progress = "";
const list = array.map((item) => {
progress += item;
let local = progress;
return (
<input
key={item}
type="button"
value={item}
onClick={() => {
console.log(local);
}}
/>
);
});
return <div className="App">{list}</div>;
}
I haven't tested this exactly, but it should work.

When your code is executed, the array.map method runs the callback function provided for every element of the array. So when your program finished executing, the value of progress will be item1item2item3 and all you're click handlers are closing over the same variable progress(In closures a reference to the variable is kept) and hence the result item1item2item3.
You can solve this by defining a new scope for every handler using the IIFE.
export default function App() {
const array = ["item1", "item2", "item3"];
var progress = "";
const list = array.map(function (item) {
progress += item;
return (
<input
key={item}
type="button"
value={item}
onClick={(function(progress){
return function () {
console.log(progress);
}
})(progress)}
/>
);
});
return <div className="App">{list}</div>;
}```

Related

Warning: Expected `onClick` listener to be a function, instead got a value of `object` type. -react

i cant runing function on render side.
here is my code:
let arr = []
const handleCountry = () => {
for (let i = 0; i < context.gameAdData.length; i++) {
context.gameAdData[i].map((element) =>
arr.push(element._fieldsProto.Country?.stringValue)
)
}
return arr
}
// handleCountry() // When I remove the command line, I get what I want, but I need to give this event to the button
and here is render side:
<>
<div className={styles.chart}>
<button className={styles.button} onClick={handleCountry()}>
Country
</button>
</div>
</>
and here is console print:
here is my code view ss
i hope i could explain, happy day
Actually you are doing it wrong. You are directly invoking the function instead of calling it on click, you need to remove the parentheses where you are calling the function on onClick event like this:
<button className={styles.button} onClick={handleCountry}>

Unable to update React.useState("") after joining all elements in array to single string

I am trying to update a React.useState("") using setDisplay in a function that takes an array of strings and joined them into a single string before setDisplay.
Without the setDisplay, I'm able to console.log() and get the joined array.
const App = () => {
const [display, setDisplay] = React.useState("");
const values = [];
const onClick = (value) => {
values.push(value);
console.log(values) // w/o setDisplay output: ["1","2","3","4"]; w/ setDisplay output: ["1"]; ["2"]; ["3"]; ["4"];
setDisplay(values.join(""))
};
return (
<div className="p-5">
{display}
<NumPads data={keypress} onClick={onClick} />
<OperatorPads data={keypress} onClick={onClick} />
</div>
);
};
setDisplay((values) => ([...values, value].join("")]))
The reason the console.log seems to work when you don't call setDisplay is simply because the state of the component did not change.
When you call setDisplay, you change the state, so the entire component re-renders, which causes values to reset back to an empty array.
Solution(s)
One way to solve this is already shown in a previous answer.
Another way is to wrap your values array with useRef:
const values = useRef([]);
Then access the array using values.current
Yet another way is to eliminate the use of values all together and change the callback to:
const onClick = (value) => {
setDisplay(display + value);
};
const onClick = (value) => setDisplay((dispay) => display.concat('', value));

Strange behaviour when removing element from array of divs (React Hooks)

I am struggling to understand a strange behaviour while deleting an element from an array of divs.
What I want to do is create an array of divs representing a list of purchases. Each purchase has a delete button that must delete only the clicked one. What is happening is that when the delete button is clicked on the purchase x all the elements with indexes greather than x are deleted.
Any help will be appreciated, including syntax advices :)
import React, { useState } from "react";
const InvestmentSimulator = () => {
const [counter, increment] = useState(0);
const [purchases, setPurchases] = useState([
<div key={`purchase${counter}`}>Item 0</div>
]);
function addNewPurchase() {
increment(counter + 1);
const uniqueId = `purchase${counter}`;
const newPurchases = [
...purchases,
<div key={uniqueId}>
<button onClick={() => removePurchase(uniqueId)}>delete</button>
Item number {uniqueId}
</div>
];
setPurchases(newPurchases);
}
const removePurchase = id => {
setPurchases(
purchases.filter(function(purchase) {
return purchase.key !== `purchase${id}`;
})
);
};
const purchasesList = (
<div>
{purchases.map(purchase => {
if (purchases.indexOf(purchase) === purchases.length - 1) {
return (
<div key={purchases.indexOf(purchase)}>
{purchase}
<button onClick={() => addNewPurchase()}>add</button>
</div>
);
}
return purchase;
})}
</div>
);
return <div>{purchasesList}</div>;
};
export default InvestmentSimulator;
There are several issues with your code, so I'll go through them one at a time:
Don't store JSX in state
State is for storing serializable data, not UI. You can store numbers, booleans, strings, arrays, objects, etc... but don't store components.
Keep your JSX simple
The JSX you are returning is a bit convoluted. You are mapping through purchases, but then also returning an add button if it is the last purchase. The add button is not related to mapping the purchases, so define it separately:
return (
<div>
// Map purchases
{purchases.map(purchase => (
// The JSX for purchases is defined here, not in state
<div key={purchase.id}>
<button onClick={() => removePurchase(purchase.id)}>
delete
</button>
Item number {purchase.id}
</div>
))}
// An add button at the end of the list of purchases
<button onClick={() => addNewPurchase()}>add</button>
</div>
)
Since we should not be storing JSX in state, the return statement is where we turn our state values into JSX.
Don't give confusing names to setter functions.
You have created a state variable counter, and named the setter function increment. This is misleading - the function increment does not increment the counter, it sets the counter. If I call increment(0), the count is not incremented, it is set to 0.
Be consistent with naming setter functions. It is the accepted best practice in the React community that the setter function has the same name as the variable it sets, prefixed with the word "set". In other words, your state value is counter, so your setter function should be called setCounter. That is accurate and descriptive of what the function does:
const [counter, setCounter] = useState(0)
State is updated asynchronously - don't treat it synchronously
In the addNewPurchase function, you have:
increment(counter + 1)
const uniqueId = `purchase${counter}`
This will not work the way you expect it to. For example:
const [myVal, setMyVal] = useState(0)
const updateMyVal = () => {
console.log(myVal)
setMyVal(1)
console.log(myVal)
}
Consider the above example. The first console.log(myVal) would log 0 to the console. What do you expect the second console.log(myVal) to log? You might expect 1, but it actually logs 0 also.
State does not update until the function finishes execution and the component re-renders, so the value of myVal will never change part way through a function. It remains the same for the whole function.
In your case, you're creating an ID with the old value of counter.
The component
Here is an updated version of your component:
const InvestmentSimulator = () => {
// Use sensible setter function naming
const [counter, setCounter] = useState(0)
// Don't store JSX in state
const [purchases, setPurchases] = useState([])
const addNewPurchase = () => {
setCounter(prev => prev + 1)
setPurchases(prev => [...prev, { id: `purchase${counter + 1}` }])
}
const removePurchase = id => {
setPurchases(prev => prev.filter(p => p.id !== id))
}
// Keep your JSX simple
return (
<div>
{purchases.map(purchase => (
<div key={purchase.id}>
<button onClick={() => removePurchase(purchase.id)}>
delete
</button>
Item number {purchase.id}
</div>
))}
<button onClick={() => addNewPurchase()}>add</button>
</div>
)
}
Final thoughts
Even with these changes, the component still requires a bit of a re-design.
For example, it is not good practice to use a counter to create unique IDs. If the counter is reset, items will share the same ID. I expect that each of these items will eventually store more data than just an ID, so give them each a unique ID that is related to the item, not related to its place in a list.
Never use indices of an array as key. Check out this article for more information about that. If you want to use index doing something as I did below.
const purchasesList = (
<div>
{purchases.map((purchase, i) => {
const idx = i;
if (purchases.indexOf(purchase) === purchases.length - 1) {
return (
<div key={idx}>
{purchase}
<button onClick={() => addNewPurchase()}>add</button>
</div>
);
}
return purchase;
})}
</div>
);

Function passed to element ref in React never runs

I'm building an array of React symbols, and passing a function to ref which never runs, for some reason. I believe I'm following the documentation on how to do this. By the time this is done rendering this.divElement should be set to the native DOM node of the last element, but since the function never runs, it remains null.
divElement = null;
render() {
const activeKeys = ["test1", "test2", "test3"];
const infoDivs = activeKeys.map((key, i) => {
console.log(key, i);
return (
<div
className="test"
ref={ (divElement) => { alert('This never runs?'); this.divElement = divElement } }
>
Some div text.
</divs>
)
});
return (
<span className="info-container container">
{ this.infoDivs }
</span>
)
}
I know the loop is running, as it logs three times as expected. What am I missing?
Apparently you meant to use {infoDivs}, not { this.infoDivs }. Your infoDivs variable that returns your elements, is just a variable inside render, not a class field.
Elements from infoDivs are never rendered, so the divElement remains undefined.
return (
<span className="info-container container">
{infoDivs}
</span>
)

Why do i need to bind this arrow function in scope of map?

I'm trying to display a array from an array object using onClick event on a li , i am using arrow function but i found out i needed to bind the function inside the scope of map , why is that? Isn't that the purpose of arrow function to eliminate the need of binding?
class ListRecipes extends React.Component {
showIngredients = (item) => {
console.log(item.ingredients)
}
render() {
const { list } = this.props;
let Recipe = list.map((item, index) => {
let boundClick = this.showIngredients.bind(this, item);
return <li key={index} onClick={boundClick}> {item.recipeName} </li>
})
return (
<div>
<ul>
{Recipe}
</ul>
</div>
)
}
}
Also what is the difference between this code that returns a new function from the above?
class ListRecipes extends React.Component {
showIngredients = (item) => (event) => {
console.log(item.ingredients)
}
render() {
const { list } = this.props;
let Recipe = list.map((item, index) => {
return <li key={index} onClick={this.showIngredients(item)}> {item.recipeName} </li>
})
return (
<div>
<ul>
{Recipe}
</ul>
</div>
)
}
}
When you write onClick={this.showIngredients(item)} you are not assigning a reference of function to onClick but calling it
You could change it to
let Recipe = list.map((item, index) => {
return <li key={index} onClick={() => this.showIngredients(item)}> {item.recipeName} </li>
})
Also with let boundClick = this.showIngredients.bind(this, item); you are binding the showIngredients function to the context and passing an extra argument to it and since bind returns a new function so , boundClick is a function which is then later assigned to onClick
I think this code is generally preferred:
let boundClick = this.showIngredients.bind(this, item);
You are binding the item to be passed to the showIngredients function. So you end up creating items.length number of separate function references, each bound to this and the current item.
Before anything else we need to understand that the passed argument in callback listener on onClick is event.
That mean
function myMethod(event) {
// only event is passed as argument
}
<div onClick={myMehtod}/>
So question is how can you pass additional argument then? Simple. Call your desired method from the listener.
function calculate(item){
// no event trace here
}
<div onClick={function(event) { calculate(item) }}/>
// OR better, fat arrow
<div onClick={event => calculate(item)}/>
Remember the fat arrow is always bind to this scope rather then to prototype.
class Component {
scopedMethod = () => {
this.name = 'John';
}
prototypeMethod() {
this.name = 'John';
}
render() {
return (
<div>
{/* works */}
<button onClick={this.scopedMethod}/>
{/* OOPS, 'this' is event rather then component */}
<button onClick={this.prototypeMethod}/>
{/* works */}
<button onClick={() => this.prototypeMethod()}/>
{/* works */}
<button onClick={this.prototypeMethod.bind(this)}/>
</div>
)
}
}
Hopefully that's brought a light in. So the question would be when and why I would to use scope binded method over prototype method and vice versa? Well, prototype methods are cheap. Always try to use them. However some developer are avoiding lambda function (generate function inside render) because it can cause performance issue. When you need to re-render your component 10x in second, then the new lambda is created 10x.

Categories

Resources