for-loop in function call with state hooks - React - javascript

I want my function to roll 5 dices and store them in my dices state.
In the recent version, i am only storing one object for the next click, but i made the for loop to generate 5 dices at once, how can i store the previous dce object(s) for the next iteration in the for loop?
import './App.css';
import Subgroup from './Subgroup'
import React, {useState} from 'react'
function App() {
const [numberRolls, setNumberRolls] = useState(3);
const [dices, setDices] = useState([]);
return (
<div className="App">
<Subgroup
numberRolls = {numberRolls}
setNumberRolls = {setNumberRolls}
dices = {dices}
setDices = {setDices}
/>
</div>
);
}
export default App;
import React from 'react'
const Subgroup = ({numberRolls, setNumberRolls, dices, setDices}) => {
const rollTheDice = () => {
if(numberRolls > 0) {
setNumberRolls(numberRolls - 1);
for(let i = 0; i < 5; i++) {
setDices([...dices, // i think sth should be different here????
{id: Math.random()*100,
number: Math.ceil(Math.random()*6)-1,
selected: false}
])
}
}
console.log(dices)
}
return (
<div>
<button onClick={ rollTheDice }>blablabala </button>
</div>
)
}
export default Subgroup;

Try saving the result in an array, then set the state, something like:
const result = [];
for(let i = 0; i < 5; i++) {
result.push({
id: Math.random() * 100,
number: Math.ceil(Math.random() * 6) -1,
selected: false
});
}
setDices(prev => [...prev, ...result]);

Related

react - not able to setValue for input box

I am making 2 otp input in my application.
In Input.tsx, I am using react-otp-input for the otp functionality
if
<OtpInput
value={"abcde"}
...
numInputs={5}
/>
The UI of react-otp-input will be
Now the problem is, when I try to change the value of otp, it throws error
Cannot set properties of undefined (setting 'value')
How can I fix it?
Input.tsx
import React, { useState } from "react";
import OtpInput from "react-otp-input";
type InputPropType = {
value: string;
setValue: (event: string) => void;
};
function Input(props: InputPropType): JSX.Element {
const { value, setValue } = props;
return (
<OtpInput
value={value}
onChange={(e: string) => {
setValue(e);
}}
numInputs={5}
/>
);
}
export default Input;
App.tsx
import React, { useState } from "react";
import Input from "./Input";
export default function App() {
type InputValueType = {
id: number;
value: string;
};
const [inputValues, setInputValues] = useState<Array<InputValueType>>([
{ id: 0, value: "" },
{ id: 1, value: "" }
]);
const InputGroup = () => {
let numOfInputs: number = 2;
var rows: Array<any> = [];
for (var i = 0; i < numOfInputs; i++) {
let inputValue: InputValueType = inputValues[i];
rows.push(
<Input
key={inputValue.id}
value={inputValue.value}
setValue={(event: string) => {
let inputValuesTemp = inputValues;
inputValuesTemp[i]["value"] = event;
setInputValues(inputValuesTemp);
}}
/>
);
}
return <>{rows}</>;
};
return (
<div className="App">
<InputGroup />
</div>
);
}
Codesandbox
https://codesandbox.io/s/react-typescript-forked-s38ck9?file=/src/App.tsx:0-918
Few things to be corrected,
There is an issue with closures in your for-loop since you have used var instead of let. All the i variables refer to the same for-loop closure in the for-loop, which means i is 2 at the end of the iteration.
All the inputValuesTemp[i] are now resolved to inputValuesTemp[2] which is definitely undefined.
Replace var with let to create closures for each iteration of the loop.
for (let i = 0; i < numOfInputs; i++) {...}
And, you also need o get a copy of the values array for react to do a rerender (to inform that the array has changed).
let inputValuesTemp = [...inputValues];
Your InputGroup component was within the App component, which caused you to lose focus for each keystroke. To fix the focus issue, move the InputGroup out of the App and keep it as a separate component.
import React, { useState } from "react";
import Input from "./Input";
type InputValueType = {
id: number;
value: string;
};
const InputGroup = () => {
const [inputValues, setInputValues] = useState<Array<InputValueType>>([
{ id: 0, value: "" },
{ id: 1, value: "" }
]);
let numOfInputs: number = 2;
var rows: Array<any> = [];
for (let i = 0; i < numOfInputs; i++) {
let inputValue: InputValueType = inputValues[i];
rows.push(
<Input
key={inputValue.id}
value={inputValue.value}
setValue={(event: string) => {
let inputValuesTemp = [...inputValues];
inputValuesTemp[i]["value"] = event;
setInputValues(inputValuesTemp);
}}
/>
);
}
return <>{rows}</>;
};
export default function App() {
return (
<div className="App">
<InputGroup />
</div>
);
}

React Hooks: Shuffle array immediately on load, and onClick

I have an array that I am trying to shuffle on initial load, and onClick. My functions seem to be working but aren't visible unless there is a page rerender.
2 issues I'm trying to resolve:
I want to shuffle on initial load, but the shuffle doesn't occur unless there is a rerender.
I want to shuffle on button press, but the shuffle doesn't occur unless there is a rerender.
Here is my CodeSandbox
Thanks
import React, {
useEffect,
useState
} from "react";
const myArray = [{
name: "cat",
count: 0
},
{
name: "dog",
count: 0
},
{
name: "hamster",
count: 0
},
{
name: "lizard",
count: 0
}
];
function shuffle(arra1) {
var ctr = arra1.length,
temp,
index;
while (ctr > 0) {
index = Math.floor(Math.random() * ctr);
ctr--;
temp = arra1[ctr];
arra1[ctr] = arra1[index];
arra1[index] = temp;
}
return arra1;
}
function App(props) {
const [list, setList] = useState(myArray);
useEffect(() => {
const mountArray = shuffle(myArray);
setList(mountArray);
}, []);
function handleShuffle() {
const changes = shuffle([...list]);
setList(changes);
console.log("Shuffle", myArray, changes);
}
return (
<div>
{list.map((x, index) => (
<div key = {x.name + x.index}>
{x.name} - {x.count}
<button onClick={() => setList([...list], (x.count = x.count + 1))}>
+
</button>
</div>
))}
<button onClick={handleShuffle}>
Shuffle
</button>
</div>
);
}
export default App;
HAI i have made some changes in App.js
import React, { useEffect, useState } from "react";
const myArray = [
{ name: "cat", count: 0 },
{ name: "dog", count: 0 },
{ name: "hamster", count: 0 },
{ name: "lizard", count: 0 }
];
function shuffle(arra1) {
var ctr = arra1.length,
temp,
index;
while (ctr > 0) {
index = Math.floor(Math.random() * ctr);
ctr--;
temp = arra1[ctr];
arra1[ctr] = arra1[index];
arra1[index] = temp;
}
return arra1;
}
function App(props) {
const [list, setList] = useState(myArray);
useEffect(() => {
const mountArray = shuffle(myArray);
setList(mountArray);
}, []);
function handleShuffle() {
const changes = shuffle([...list]);
setList(changes);
console.log("Shuffle", myArray);
}
return (
<div>
{list.map((x, index) => (
<div key={x.name + x.index}>
{x.name} - {x.count}
<button onClick={() => setList([...list], (x.count = x.count + 1))}>
+
</button>
</div>
))}
<button onClick={handleShuffle}>Shuffle</button>
</div>
);
}
export default App;
Your setList function is used to modify the array and returns a new array, with shuffled values, so you need to apply that function non initial rendering.
useEffect(() => {
setList(shuffle(myArray))
}, []);
Html changes only if the state has changed, so make some state inside a component, and update it every time you want to update the html.
function App(props){
const [myArray, setMyArray] = useState([])
// rest of the code
}

Why is my for loop exiting before my condition

import ReactDOM from "react-dom";
import React, { useState } from "react";
const App = () => {
let [persons, setPersons] = useState([{ id: 11, name: "Arto Hellas" }]);
const [newName, setNewName] = useState("");
const check = (arr, name) => {
let i = 0;
let checking = true;
for (i = 0; i < arr.length; i++) {
console.log(arr[i].name === name);
if (arr[i].name === name) {
checking = false;
break;
}
console.log(i);
return checking;
}
};
const addName = event => {
event.preventDefault();
const nameObject = {
id: newName.length + 1,
name: newName
};
check(persons, nameObject.name)
? setPersons((persons = persons.concat(nameObject)))
: window.alert(`${nameObject.name} is already listed`);
setNewName("");
console.log(JSON.stringify(persons));
};
const handleNameChange = event => {
setNewName(event.target.value);
};
return (
<div>
<h2>Phonebook</h2>
<form onSubmit={addName}>
<div>
name: <input value={newName} onChange={handleNameChange} />
</div>
<div>
<button type="submit">add</button>
</div>
</form>
<h2>Numbers</h2>
<ul>
{persons.map(person => (
<li key={person.id}>{person.name} </li>
))}
</ul>
</div>
);
};
export default App;
const rootElement = document.getElementById("root");
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
rootElement
);
The problem is with my check function which checks if a name exists in the array. I have a for loop that iterates throughout the array but it always stops early. I checked this with console.log(i). If you add Adib once the array looks like this [{"id":11,"name":"Arto Hellas"},{"id":5,"name":"Adib"}] and the value of i is 0 since because before this the length of arr was 1. However if you add Adib again it will do so and value of i is 0 again and not 1
You have return checking in loop. Just put it after }:
const check = (arr, name) => {
let i = 0;
let checking = true;
for (i = 0; i < arr.length; i++) {
console.log(arr[i].name === name);
if (arr[i].name === name) {
checking = false;
break;
}
console.log(i);
}
return checking;
};
See full example in playground: https://jscomplete.com/playground/s522611
I'm sorry because I'm not on VScode right now I don't have a plugin which lets me see my brackets more clearly and accidentally had my return statement in a for loop.

Generating inputs in a React component

I am trying to generate inputs on a button click and the amount of inputs is generated by a random number. Here is what I have so far, but it isn't working. I am very confused and feel like it should be working. I am not sure what I am missing. Any help would be very much appreciated.
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import { Image } from './Image.js'
import { Button } from './Button.js'
import { images } from './assets/images.js'
import { Countdown } from './Countdown.js'
import { DisplayCount } from './DisplayCount.js'
import { Inputs } from './Inputs.js'
class Game extends React.Component{
constructor(props){
super(props)
this.timer = null
this.state = {
currentImg: 0,
timer: null,
ranNum: null
}
this.handleClick = this.handleClick.bind(this)
}
countdownClock = async (newRanNum) => {
const startingNum = newRanNum * 20;
for(let i = startingNum; i >= 0; i--) {
await new Promise(resolve => {
this.timer = setTimeout(() => {
this.setState({
timer: i
})
resolve()
}, 1000)
});
}
}
generateInputs = (newRanNum) => {
const inputs = []
for(let i = 1; i <= newRanNum; i++){
inputs.push(
<Inputs type='text' className='textInputs' />
)
}
return inputs;
}
handleClick(){
clearTimeout(this.timer)
let newRanNum = Math.floor(Math.random() * 20);
this.countdownClock(newRanNum)
this.generateInputs(newRanNum)
let current = this.state.currentImg;
let next = ++current % images.length;
this.setState({
currentImg: next,
ranNum: newRanNum
})
}
render(){
let src = this.state.currentImg;
return(
<div>
<Countdown name={'Countdown: '} countdown={this.state.timer} />
<DisplayCount name='Word Count: ' count={this.state.ranNum} />
<Image src={images[src]} />
<Button onClick={this.handleClick} />
<div>
<ul>
{this.generateInputs()}
</ul>
</div>
</div>
)
}
}
ReactDOM.render(
<Game />,
document.getElementById('root')
);
Inputs component:
import React from 'react'
export const Inputs = (props) => {
return (
<li className={props.className}>
<input value={props.value} />
</li>
)
}
I believe the issue is here...
generateInputs = (newRanNum) ...
and here...
{this.generateInputs()}
You're input rendering function is expecting a parameter it's not getting, and since that returns as undefined the loop never runs. :)
In general, it is most common to separate state (eg, number of inputs) from the rendering, and only have the rendering respond to state. Perhaps you had originally intended generateInputs() to produce the array, and not render them (?)
The first thing is your generateInputs() function takes a parameter for number of inputs you wanted to render but you are not passing any parameter to this.generateInputs()
The second thing is you are returning an array from this function but not mapping your array in the render function, So do in this way.
this.generateInputs(10) // pass parameter here
this.generateInputs(10).map((item, index)=>item)
In this Snack, I have done the same thing but with react-native
https://snack.expo.io/#waheed25/smiling-carrot

How to update one child component alone in React Js

I was making react version of game Bingo. I added 25 buttons which are the child components.
Initially i have empty values in each button. Then on each click, i was trying to update the clicked button's values from 1 to 25.
But when i click on one button, to update the button label, all button's values are getting updated. Can anyone suggest the reason behind that?
App.js
import React from "react";
import "./styles.css";
import GameContainerOne from "./GameContainerOne";
export default function App() {
return (
<div className="App">
<GameContainerOne />
</div>
);
}
GameContainerOne.js
import React from "react";
import ButtonBox from "./ButtonBox";
class GameContainerOne extends React.Component {
constructor(props) {
super(props);
this.state = {
btnLabel: 0
};
}
handleClicked = () => {
if (this.state.btnLabel < 25) {
this.setState({ btnLabel: ++this.state.btnLabel });
console.log("after", this.state.btnLabel);
}
};
render() {
let menuItems = [];
let key = 0;
for (let i = 0; i < 5; i++) {
for (let j = 0; j < 5; j++) {
let index = "" + i + j;
// console.log(index)
key++;
menuItems.push(
<ButtonBox
key={key}
index={index}
value={this.state.btnLabel}
handleClicked={this.handleClicked.bind(this)}
/>
);
}
}
return <div className="wrapper">{menuItems}</div>;
// this.handleButtonBox()
}
}
export default GameContainerOne;
ButtonBox.js
import React from "react";
import Button from "#material-ui/core/Button";
class ButtonBox extends React.Component {
constructor(props) {
super(props);
this.state = {
initialBtnColor: "Default",
selectedBtnColor: "Primary"
};
}
handleClick = () => {
// console.log("before",this.state.btnLabel)
this.setState({ initialBtnColor: "Primary" });
return this.props.handleClicked;
};
render() {
console.log("Key=", this.props);
// const { index } = this.props.index;
console.log("Key=", this.props.index);
return (
<div>
<Button
variant="contained"
color={this.state.initialBtnColor}
onClick={this.props.handleClicked}
>
{this.props.value}
</Button>
</div>
);
}
}
export default ButtonBox;
Please find the codesandbox link : https://codesandbox.io/s/bingo-game-glk8v
Move the btnLabel state into the ButtonBox.
Example (using hooks):
// ButtonBox.js
import React from "react";
import Button from "#material-ui/core/Button";
function ButtonBox(props) {
const [buttonColor, setButtonColor] = React.useState("Default");
const [value, setValue] = React.useState(props.startValue);
return (
<div>
<Button
variant="contained"
color={buttonColor}
onClick={() => {
setValue(value + 1);
setButtonColor("Primary");
props.handleClicked(props.index);
}}
>
{value}
</Button>
</div>
);
}
export default ButtonBox;
// GameContainerOne.js
import React from "react";
import ButtonBox from "./ButtonBox";
function GameContainerOne(props) {
const handleClicked = React.useCallback(btnIndex => {
// Called after a button is clicked
}, []);
let menuItems = [];
let key = 0;
for (let i = 0; i < 5; i++) {
for (let j = 0; j < 5; j++) {
let index = "" + i + j;
key++;
menuItems.push(
<ButtonBox
key={key}
index={index}
startValue={0}
handleClicked={handleClicked}
/>
);
}
}
return <div className="wrapper">{menuItems}</div>;
}
export default GameContainerOne;
You change the state on click which is the source of labels for all the buttons.
You could have a separate state for all the buttons to avoid that and changed them based on the index on the button clicked.

Categories