React event-handler shows the same index value - javascript

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

Related

for loop wont work in a function inside react component

import React from 'react';
const RowArray=()=>{
return(
<div>
<h1>Row Array</h1>
</div>
)
};
const chunk_array = (list, integer)=>{
let temp_arr = list;
console.log('chunks',list,'integer',integer);
const list_of_chunks = [];
const iteration = Math.ceil(+list.length/+integer);
// list.map(x => {console.log(x,"map")})
for (let i;i< iteration ;i++ ){
console.log(i);
let temp_chunk = temp_arr.splice(6, temp_arr.length);
list_of_chunks.push(temp_chunk);
};
return list_of_chunks;
}
const TableArray=({details})=>{
const data = chunk_array(details);
console.log('data', data);
return(
<div className="d-flex flex-row">
<RowArray/>
</div>
)
};
export default TableArray;
the for loop in function chunk array won't work, supported as no i was logged in the console. I understand in jsx for loop may not work, I believe I define the function in pure javascript enviroment, so why do you think it is?
Console.log(i) doesn't log anything, as in the function skipped for loop line
you haven't initialized the value of i in the for loop
for (let i = 0; i < iteration; i++) {
// your code
}
chunk_array function expects two arguments and you're only passing one argument details

React child not updating a variable of parent through function

So I'm learning react at the moment and I've been struggling with this issue..
I'm trying to do tic-tac-toe so I've got this code:
import './App.css';
import { useState } from "react"
const X = 1;
const O = -1;
const EMPTY = 0;
var Square = ({idx, click}) =>
{
let [val, setVal] = useState(EMPTY);
return (
<button onClick={() => {click(setVal, idx);}}>{val}</button>
)
}
var Logger = ({state}) =>
{
return (
<button onClick={() => {console.log(state);}}>log</button>
)
}
var App = () =>
{
let [turn, setTurn] = useState(X);
let state = new Array(9).fill(EMPTY);
let squares = new Array(9);
let click = (setValFunc, idx) =>
{
setTurn(-turn);
setValFunc(turn);
state[idx] = turn;
}
for (let i = 0 ; i < 9 ; i++)
{
squares[i] = (<Square click={click} idx={i}/>);
}
return (
<>
<Logger state={state} />
<div>
{squares}
</div>
</>
)
}
export default App;
so the squares ARE changing as I click them, but when I click the log button to log the state array to the console, the state array remains all zeros.
what am I missing here?
Your state has to be a React state again. Otherwise, the state you defined inside the App as a local variable only lasts until the next rerender.
Maintain tic-tac-toe state inside a useState hook
let [state, setState] = useState(new Array(9).fill(EMPTY));
Update the click handler accordingly.
let click = (setValFunc, idx) => {
setTurn(-turn);
setValFunc(turn);
setState((prevState) =>
prevState.map((item, index) => (index === idx ? turn : item))
);
};
In React, the state concept is important.
In your case, you need to understand what is your state and how you can model it.
If you are doing a Tic-Tac-Toe you will have:
the board game: a 3 by 3 "table" with empty, cross or circle signs
This can be modeled by an array of 9 elements as you did.
But then you need to store this array using useState otherwise between -re-renders your state array will be recreated every time.
I advise you to read https://reactjs.org/docs/lifting-state-up.html and https://beta.reactjs.org/learn.

How to properly use a for loop in getting the values of inputs in React.js?

The following code has a button to add inputs and another button to add all of the values in the inputs assuming they are numbers. The last line is to display the sum of everything. However, for some reason it only gets the value of the last input. It also does not reset. I thought having the setGrade(0) would do this but it just keeps adding the last number without resetting.
I would just like to know why this is the case with the following code. The id for the input fields are just the number starting from 1.
function Start(){
const [rows, setRows] = useState([]);
const [inputNum,setNum] = useState(1);
const [totalGrade, setGrade] = useState(0);
const addInput = () =>{
setNum(inputNum+1);
setRows(rows.concat(<Inputs key={rows.length} label={inputNum.toString()}></Inputs>));
}
const addGrade = () =>{
setGrade(0);
for(let i =0;i<rows.length;i++){
setGrade(parseInt(document.getElementById((i+1).toString()).value,10) +totalGrade)
}
}
return(
<div>
<h1>Calculate your GPA!</h1>
{rows}
<button class="button2"onClick={addInput}>Add Input</button>
<button class="button2"onClick={addGrade}>Compute Grade</button>
<h2>Grade: {totalGrade}</h2>
</div>
);
}
You shouldn't be mixing native element methods like getElementById in React code.
Add an onChange directly onto the input elements.
Create a new state (an object) that maintains a record of each change to an input (identified by key/id) where the value is the new value of the input.
Don't set state in a loop - it's bad practice.
Here's how I've approached it (I've had to simplify the example as I don't have access to the Inputs component.)
const { useEffect, useState } = React;
function Example() {
const [inputNum, setInputNum] = useState(0);
// The new state which maintains all the input values
const [inputData, setInputData] = useState({});
const [totalGrade, setTotalGrade] = useState(0);
// `addInput` is now only responsible
// for updating the number of rows
function addInput() {
setInputNum(inputNum + 1);
}
// NEW FUNCTION: it handles the update of the
// `inputData` state. It grabs the id and value from
// the input, and then updates the state with that
// new information
function handleChange(e) {
const { id, value } = e.target;
// Take the previous state (object), and update it
// by spreading (copying) out the previous object,
// and adding a new property with the id as key
// and the value as the value.
setInputData(prev => ({ ...prev, [id]: value }));
}
// `sumGrade` - I renamed this - grabs the Object.values
// of the inputData state and then creates a sum of all
// of those values using `reduce`. It then, finally, sets
// the `totalGrade` state.
function sumGrade() {
const values = Object.values(inputData);
const result = values.reduce((acc, c) => {
return acc + +c;
}, 0);
setTotalGrade(result);
}
// NEW FUNCTION: this builds an array of new inputs
// which can be used in the JSX
function buildRows() {
const arr = [];
for (let i = 0; i < inputNum; i++) {
arr.push(<input onChange={handleChange} type="number" key={i} id={i} value={inputData[i]}/>);
}
return arr;
}
return (
<div>
<h1>Calculate your GPA!</h1>
{buildRows()}
<button class="button2"onClick={addInput}>Add Input</button>
<button class="button2"onClick={sumGrade}>Compute Grade</button>
<h2>Grade: {totalGrade}</h2>
</div>
);
}
// Render it
ReactDOM.render(
<Example />,
document.getElementById("react")
);
input { display: block; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.production.min.js"></script>
<div id="react"></div>

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.

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

Categories

Resources