React child not updating a variable of parent through function - javascript

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.

Related

How to set state with a setInterval() when a button is clicked?

I'm trying to update the count of activeIndex within the setInterval when the checkbox is ticked. handleNext() and handlePrevious() are working fine when buttons are clicked but when the checkbox is checked the value of activeIndex is not getting updated in handleNext() but it's getting updated on the screen, so there is no condition check for activeIndex and it goes beyond 3.
import { useState } from "react";
import "./styles.css";
export default function App() {
const [slideTimer, setSlideTimer] = useState(null);
const [activeIndex, setActiveIndex] = useState(0);
const slideDuration = 1000;
const handleNext = () => {
if ((activeIndex) >= 3) {
setActiveIndex(0);
} else {
setActiveIndex(prev => prev + 1);
}
};
const handlePrev = () => {
if (activeIndex <= 0) {
setActiveIndex(3);
} else {
setActiveIndex((prev) => prev - 1);
}
};
const toggleSlider = () => {
if (slideTimer) {
clearInterval(slideTimer);
setSlideTimer(null);
return;
}
setSlideTimer(setInterval(handleNext, slideDuration));
};
return (
<div className="App">
<h1>{activeIndex}</h1>
<button onClick={() => handleNext()}>Next</button>
<button onClick={() => handlePrev()}>Previous</button>
<input onClick={() => toggleSlider()} type="checkbox" />
</div>
);
}
I have tried putting the code for toggleSlider inside useEffect() but that too is not working.
Your problem is that when handleNext gets defined (which occurs on every rerender), it only knows about the variables/state in its surrounding scope at the time that it's defined. As a result, when you queue an interval with:
setInterval(handleNext, slideDuration)
You will be executing the handleNext function that only knows about the component's state at that time. When your component eventually rerenders and sets a new value for activeIndex, your interval will still be exeucting the "old" handleNext function defined in the previous render that doesn't know about the newly updated state. One option to resolve this issue is to make the hanldeNext function not rely on state obtained from its outer scope, but instead, use the state setter function to get the current value:
const handleNext = () => {
setActiveIndex(currIndex => (currIndex + 1) % 4);
};
Above I've used % to cycle the index back to 0, but you very well can also use your if-statement, where you return 0 if currIndex >= 3, or return currIndex + 1 in your else.
I would also recommend that you remove the slideTimer. This state value isn't being used to describe your UI (you can see that as you're not using slideTimer within your returned JSX). In this case, you're better off using a ref to store your interval id:
const slideTimerRef = useRef(0); // instead of the `slideTimer` state
...
clearInterval(slideTimerRef.current);
slideTimerRef.current = null;
...
slideTimerRef.current = setInterval(handleNext, slideDuration);

Recurring function with useState - React

I have read up on hashing iteration and although this is not a question about security I can´t seem to find information on how to actually do this correct.
I thought that when in React, this should be done with useState, but im clearly missing something here.
Full code:
import { sha256 } from "js-sha256";
import { useState } from "react";
function App() {
const [currentHash, setCurrentHash] = useState("summer1");
function iterate(iterations) {
for (let x = 0; x < iterations; x++) {
setCurrentHash(sha256(currentHash)); //Does 1 hash only
console.log("Hashed", x, "times"); //Logs 4 times
}
}
return (
<div className="App">
<button onClick={() => iterate(4)}> Klick</button>
currentHash: {currentHash}
{/* Correct hashes */}
<p>0: summer1</p>
<p>1: {sha256("summer1")}</p>
<p>2: {sha256(sha256("summer1"))}</p>
<p>3: {sha256(sha256(sha256("summer1")))}</p>
<p>4: {sha256(sha256(sha256(sha256("summer1"))))}</p>
</div>
);
}
export default App;
sha256(currentHash) is going to give you the same result no matter how many times you run it.
currentHash won't be updated until the component is re-rendered and a new value is pulled out of the state and assigned to the new instance of currentHash at the top of the function.
You need to:
store the result in a variable — not the state
use the value of that variable as the input to the function
store the final result in the state at the end
Working code:
import { sha256 } from "js-sha256";
import { useState } from "react";
function App() {
const [currentHash, setCurrentHash] = useState("");
function iterate(input, iterations) {
for (let x = 0; x < iterations; x++) {
input = sha256(input);
console.log("Hashed", x, "times");
}
setCurrentHash(input)
}
return (
<div className="App">
<button onClick={() => iterate("summer1", 2)}> Klick</button>
currentHash: {currentHash}
</div>
);
}
export default App;

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>

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

Component does not render, but react does not show errors

I am trying to create an application that will sort the array from the smallest to the largest, but at the very beginning I encountered an error. React does not show a single error and the component does not render anyway.
App.js
import { SortingVizualize } from './SortingVizualize/SortingVizualize';
function App() {
return (
<div className="App">
<SortingVizualize />
</div>
)
}
export default App;
SortingVizualize.jsx
import React from 'react';
// import styles from './SortingVizualize.modules.scss';
export class SortingVizualize extends React.Component {
constructor(props) {
super(props);
this.state = {
array: [],
};
}
componentDidMount() {
this.resetArray();
}
resetArray() {
// I use this method to generate new array and reset
const array = [];
for (let i = 0; i < 100; i++) {
array.push(randomInt(5, 750)); // Min and Max value of number in array
}
}
render() {
const { array } = this.state;
return (
<>
{array.map((value, idx) => (
<div className="array-bar" key={idx}>
{value}
</div>
))}
</>
)
}
}
function randomInt(min, max) {
return Math.floor(Math.random() * (max - min + 1) + min)
}
export default SortingVizualize;
Your state.array and the array in resetArray are two different arrays and you never update the one in the state.
You will need to call setState to update the state
resetArray() {
// I use this method to generate new array and reset
const array = [];
for (let i = 0; i < 100; i++) {
array.push(randomInt(5, 750)); // Min and Max value of number in array
}
this.setState({ array });
}
You're not triggering any update events since you're using array.push to a local variable, which then isn't assigned to the component state. Remember to use this.setState(...) to update the array saved in your state, like so:
resetArray() {
// I use this method to generate new array and reset
const array = [];
for (let i = 0; i < 100; i++) {
array.push(randomInt(5, 750)); // Min and Max value of number in array
}
this.setState({
array,
});
}
In your resetArray method you're not assigning your data to your state. Your state's array field still empty then. You may add this.setState({ array }) in order to trigger the rerender with generated data.

Categories

Resources