I'm trying to make an app where the user can cycle through a pack of cards. I'm trying to make it so each card is unique so that a user cannot pick two of the same card consecutively. Therefore the way I thought of doing this was by adding all the cards to an array and then shuffling that array.
import * as React from "react";
import { View, Text, Button } from "react-native";
import homeStyles from "../styles/homeStyles";
import { cardsList } from "../assets/cardsList";
import { useEffect, useState } from "react";
export default function Home() {
const [CurrentQuestion, setCurrentQuestion] = useState(1);
const [usedQuestions, setUsedQuestions] = useState([]);
const test = [];
const length = cardsList.length;
const shuffle = () => {
let i,
x = 0,
y = 0,
temp0 = 0;
for (i = 0; i < usedQuestions.length; i++) {
x = Math.floor(Math.random() * usedQuestions.length);
y = Math.floor(Math.random() * usedQuestions.length);
if (x === y) {
//for dont change arr[index] with self !!!
continue;
}
temp0 = usedQuestions[x];
usedQuestions[x] = usedQuestions[y];
usedQuestions[y] = temp0;
}
console.log("Shuffled : " + usedQuestions);
};
let i = 0;
const nextQuestion = () => {
let plz = null;
if (i <= usedQuestions.length) {
// plz = usedQuestions[i]
setCurrentQuestion(usedQuestions[i]);
i = i + 1;
} else {
i = 0;
// shuffle()
// nextQuestion()
}
// console.log(plz)
console.log(CurrentQuestion);
};
const start = () => {
let i = 1;
while (i <= length) {
test.push(i);
i++;
}
console.log("initial : " + test);
setUsedQuestions([...usedQuestions, ...test]);
};
return (
<View style={homeStyles.container}>
<View style={homeStyles.card}>
{cardsList
.filter(function (item) {
return item.id === CurrentQuestion;
})
.map((cardsList) => {
return (
<View key={cardsList.id}>
<View style={homeStyles.line}>
<Text style={homeStyles.cardNumber}>
{cardsList.id}) {cardsList.title}
</Text>
</View>
<Text style={homeStyles.cardText}>{cardsList.cardText}</Text>
</View>
);
})}
<Button
style={homeStyles.button}
title="Shuffle"
onPress={() => shuffle()}
/>
<Button
style={homeStyles.button}
title="Next Question"
onPress={() => nextQuestion()}
/>
<Button
id="test"
style={homeStyles.button}
title="Start"
onPress={() => start()}
/>
</View>
</View>
);
}
The section below is the section causing me issues
let i = 0;
const nextQuestion = () => {
let plz = null;
if (i <= usedQuestions.length) {
// plz = usedQuestions[i]
setCurrentQuestion(usedQuestions[i]);
i = i + 1;
} else {
i = 0;
// shuffle()
// nextQuestion()
}
// console.log(plz)
console.log(CurrentQuestion);
};
The const plz works perfectly returning the value of the cards one by one from the shuffled array. See Image
However when I try and use the UseState feature from react to set this value so that I can use it to display the correct part in the app section it breaks.
Any idea why this happens and how I can fix it? I'm fairly new to coding so any tips on how I could better it would be appreactiated.
You cannot modify a state directly
You can create a copy, shuffle it and reassign the state in this way
const shuffle = () => {
setUsedQuestion(u => {
const data = [...u]
data.sort(() => Math.random() - 0.5))
console.log("Shuffled : " + data);
return data;
}
)
}
Everytime you update a react state (eg. by calling setCurrentQuestion) your component will rerender.
Here, rerendering means it will execute the code of your function component again (function Home()).
In the body of function Home() you have this: let i = 0; so i will reset everytime you change a local state.
Now what you probably want is to persist the current question index across multiple rerenders.
you could do nextQuestion like this:
// remove: const [CurrentQuestion, setCurrentQuestion] = useState(1);
const [currentQuestionIndex, setCurrentQuestionIndex] = useState(0);
const currentQuestion = usedQuestions[currentQuestionIndex];
const nextQuestion = () => {
if (currentQuestionIndex < usedQuestions.length) {
setCurrentQuestionIndex(currentQuestionIndex + 1);
} else {
setCurrentQuestionIndex(0);
}
};
You might have more tweaking to do but that should get you started
Related
I have tried to implement drench game. I have attached my code below. The logic is correct and it shouldn't also take so much time for execution given it is a 14x14 board. But after some clicks when the state.covered covers about quarter of the board the execution is taking way long to complete.
import './App.css';
import { useState } from 'react'
function App() {
const colors = ['aliceblue', 'tomato', 'darksalmon', 'thistle', 'olivedrab', 'steelblue']
var dp = [];
for(let i=0;i<14;i++){
let temp = []
for(let j=0;j<14;j++){
temp[j] = colors[Math.floor(Math.random() * 6)]
}
dp.push(temp)
}
var [state, setState] = useState({
boardColors: dp,
covered : [{x:0,y:0}],
})
var changeState = (color) => {
var prevcolor = state.boardColors[0][0]
var q = [...state.covered]
var append_arr = [...state.covered]
function isnotcovered(l,m,xt){
for(let i=0;i<xt.length;i++){
if(xt[i].x===l && xt[i].y===m){
return false;
}
}
return true;
}
var temp2D = [...state.boardColors];
while(q.length){
let top = q.shift()
let l = top.x;
let m = top.y;
if( m-1>0 && temp2D[l][m-1]===prevcolor && isnotcovered(l,m-1,append_arr)){
q.push({x:l,y:m-1})
append_arr.push({x:l,y:m-1})
}
if(l+1<14 && temp2D[l+1][m]===prevcolor && isnotcovered(l+1,m,append_arr)){
q.push({x:l+1,y:m})
append_arr.push({x:l+1,y:m})
}
if(m+1<14 && temp2D[l][m+1]===prevcolor && isnotcovered(l,m+1,append_arr)){
q.push({x:l,y:m+1})
append_arr.push({x:l,y:m+1})
}
if(l-1>0 && temp2D[l-1][m]===prevcolor && isnotcovered(l-1,m,append_arr)){
q.push({x:l-1,y:m})
append_arr.push({x:l-1,y:m})
}
}
setState((state) => ({boardColors : temp2D, covered : [...state.covered, ...append_arr]}) )
setState((state) => updateState(state))
function updateState(state){
function isnotcovered(l,m,xt){
for(let i=0;i<xt.length;i++){
if(xt[i].x===l && xt[i].y===m){
return false;
}
}
return true;
}
var temp2D = [...state.boardColors];
for(let i=0; i<state.covered.length; i++){
temp2D[state.covered[i].x][state.covered[i].y]=color
}
var q = [...state.covered]
var append_arr = [...state.covered]
while(q.length){
let top = q.shift()
let l = top.x;
let m = top.y;
if( m-1>0 && temp2D[l][m-1]===color && isnotcovered(l,m-1,append_arr)){
q.push({x:l,y:m-1})
append_arr.push({x:l,y:m-1})
}
if(l+1<14 && temp2D[l+1][m]===color && isnotcovered(l+1,m,append_arr)){
q.push({x:l+1,y:m})
append_arr.push({x:l+1,y:m})
}
if(m+1<14 && temp2D[l][m+1]===color && isnotcovered(l,m+1,append_arr)){
q.push({x:l,y:m+1})
append_arr.push({x:l,y:m+1})
}
if(l-1>0 && temp2D[l-1][m]===color && isnotcovered(l-1,m,append_arr)){
q.push({x:l-1,y:m})
append_arr.push({x:l-1,y:m})
}
}
return {
boardColors : temp2D,
covered : [...state.covered, ...append_arr],
}
}
}
return (
<>
<Board colors2D={state.boardColors} />
<Controls boardColors={state.boardColors} colors2D={state.boardColors} colors={colors} color='green' changeState={changeState}/>
</>
);
}
function Board(props){
var boardStyle = {
height: '280px',
aspectRatio: '1',
backgroundColor: 'lightgreen'
}
var display = props.colors2D.map((color1D, index1) => (color1D.map((color,index2) => <div key={14*index1 + index2} style={{width:'20px', aspectRatio: '1', backgroundColor: color, float: 'left'}}></div>)))
return(
<div style={boardStyle}>
{display}
</div>
)
}
function Controls(props){
var controlStyle = {
height: '200px',
width: '300px',
backgroundColor: props.color
}
var handleClick = (color) => {
props.changeState(color)
}
var buttons = props.colors.map((color, index) => <button key={index} onClick={() => handleClick(color)}>{color}</button>)
return(
<>
<div style={controlStyle}>
{buttons}
</div>
</>
)
}
export default App;
replace this code in App.js and it should work fine
I think I've found the problem by doing a console.log of your state everytime it changes:
useEffect(() => {
console.log(state);
}, [state]);
Everytime you click on a color button (even if it's the same), the displayed state shows an array as value of the covered key that gets its size multiplied by 4.
This is because you are copying the content of covered boxes in append_arr variable:
var append_arr = [...state.covered]
And when the computing is done you append the content of state.covered and the content of append_arr into state.covered:
covered : [...state.covered, ...append_arr],
This makes it at least double at every click.
You should assign only the result of append_arr as the new covered state:
covered : [...append_arr],
And the reason it actually quadruples is because you are doing this twice, with 2 functions changeState and updateState doing more or less the same thing and called each time.
You are callign setState twice. This can't be useful because the result of the first call will be applied only when the whole function finishes. You should remove one of the occurrences:
/* removing this one
setState((state) => ({boardColors : temp2D, covered : [...state.covered, ...append_arr]}) ) */
setState((state) => updateState(state))
any library or any custom code that helps to do that tell us. I tried some libraries but that not work properly.
Here is an example on how to achieve this using React
import { useEffect, useState } from "react";
function App() {
const [typewriter_text, setTypeWriterText] = useState("");
const text_to_type = "Hello there adventurer~";
let typewriter_timer;
const typingAnimation = (i = 0) => {
if (i < text_to_type.length) {
setTypeWriterText(text_to_type.substring(0, i + 1));
typewriter_timer = setTimeout(() => typingAnimation(i + 1), 100);
}
};
useEffect(() => {
typingAnimation();
return () => {
clearTimeout(typewriter_timer);
};
}, []);
return <div>{typewriter_text}</div>;
}
export default App;
Basically the idea is to add the text one by one with a setTimeout function acting as a delay.
const typingAnimation = (i = 0) => {
if (i < text_to_type.length) {
setTypeWriterText(text_to_type.substring(0, i + 1));
typewriter_timer = setTimeout(() => typingAnimation(i + 1), 100);
}
};
The function above will add the letters in the text to type one. It calls it self again after a delay. i here is like an index that keep tracks of what the previous letter is.
When I press a key using keyEvent, then I call the function, it is below.
const GeneratedKey: FC<IGenerated> = (props) => {
const [keyBoard, setKeyBoard] = useState("")
const [arrayMovie, setArrayMovie] = useState<string[]>([])
const idPage = props.idpage
const nameMovie = data.results[idPage].title
const [idLetter, setIdLetter] = useState<number>(0)
const [indexArray, setIndexArray] = useState<number[]>([])
const arrayNameMovie = nameMovie.split(" ").join("").split("");
const getKey = (e: any) => {
console.log("test")
const key = e.key
let count = 0
for (let i = 65; i <= 90; i++) {
if (key.toUpperCase() == String.fromCharCode(i)) {
count = 1
} else if (key.toLowerCase() == "backspace") {
count = 10
}
}
if (count == 1) {
indexArray.sort(function (a: any, b: any) {
return a - b;
})
arrayMovie.splice(indexArray[idLetter], 1, key)
setIdLetter(idLetter + 1)
} else if (count == 10) {
if (idLetter >= 1) {
setIdLetter(idLetter - 1)
arrayMovie.splice(indexArray[idLetter], 1, "")
}
}
setKeyBoard(key)
document.removeEventListener("keydown", getKey);
}
useEffect(() => {
for (let i = 3; i >= 0; i--) {
const randomIndex = Math.floor(Math.random() * arrayNameMovie.length)
arrayNameMovie.splice(randomIndex, 1, " ")
indexArray.push(randomIndex)
}
setIdLetter(indexArray[0])
setArrayMovie(arrayNameMovie)
}, [])
document.addEventListener("keydown", getKey)
return (
<div className="down__word">
{arrayMovie.map((letter: any) =>
<LettersView letters={letter} props={undefined} />
)}
</div>
)
}
In fact, it should be called once, but it fires twice, as you can see from console.log();
How can I fix this, I can also show other files with code, but this is unlikely to help
This is due to your component may get rendered twice (due to change of props or any external reason). Moreover I think this is not the correct way to handle the event listener in FC. You should consider the useEffect for registration / un-registration of event listener.
useEffect(() => {
const handler = () => {};
document.addEventListener('keydown', handler);
return () => {
document.removeEventListener('keydown', handler);
}
}, [deps]); // if you have any changing dependency else just remove the array to execute the UseEffect everytime.
This will ensure that you have registered the event only once in your code.
Refer https://reactjs.org/docs/hooks-effect.html
I am currently trying to build an next.js app.
So what I want to do is passing a function to a component and call it with useEffect
I have the following component in order to change the props of the parents element. Therefore I pass the function from the parent like this:
<NumberInput name="height" update={manuelUpdate}></NumberInput>
The manuelUpdate function is a another props function from the actual parent:
const manuelUpdate = (name, value) => {
props.update(name, value);
};
It works fine if I run the props.function with the onclick functions. However as soon as I try to use it in useEffect, it returns that the function is not a function.
Maybe I am just thinking to complicated..
this is the component;
const NumberInput = ({ name, min = undefined, max = undefined, ...props }) => {
const minInput = min !== undefined
? min
: null;
const maxInput = max !== undefined
? max
: null;
const [numb, setNumb] = useState(0);
useEffect(() => {
console.log(props.update)
}, [numb]);
const increaseNumb = () => {
if (numb < maxInput || maxInput == null) {
setNumb(numb + 1)
}
props.update(name, numb)
};
const decreaseNumb = () => {
if (numb < minInput || minInput == null) {
setNumb(numb - 1)
}
};
const changeHandler = ({ e }) => {
let n = parseInt(e.target.value)
if (Number.isNaN(n)) {
setNumb(numb)
} else {
setNumb(n)
}
}
return (
<div className={styles.def_number_input, styles.number_input}>
<button onClick={decreaseNumb} className={styles.minus}></button>
<input className={styles.quantity} name="quantity" value={numb} onChange={(e) => changeHandler({ e })}
type="number" />
<button onClick={increaseNumb} className={styles.plus}></button>
</div>
);
};
sorry in advance if my question is stupid or my code messy, I am still in the process of learning :D
Issue was related to another problem. I was accidentally calling the component 2 times and forgot to adjust the parameters. CASE CLOSED
I have a use case where I have 4 select components all sharing the same state. The problem is when the user selects an option from one select, the other selects should not show that option and so on. That problem is simple and solved. On doing so I am updating the shared state and hence all 4 selects are re-rendered by react and hence I am losing the selected value from the select. See attached GIF.
Can someone point out a solution where I can solve this using minimum states and less re-rendering?
Attaching the whole code below:
const SendTroops = () => {
const [planets, setPlanets] = useState([]);
const [vehicles, setVehicles] = useState([]);
const constructState = (data) => {
const arr = [];
let obj = {};
for (let i = 0; i < 4; i++) {
obj[i] = data;
}
arr.push(obj);
return arr;
};
useEffect(() => {
const getPlanets = async () => {
try {
const response = await callAPI(PLANETS.url, PLANETS.method);
const data = await response.json();
setPlanets(constructState(data));
} catch (err) {
console.log(err);
}
}
const getVehicles = async () => {
try {
const response = await callAPI(VEHICLES.url, VEHICLES.method);
const data = await response.json();
setVehicles(constructState(data));
} catch (err) {
console.log(err);
}
};
getPlanets();
getVehicles();
}, []);
const ShowInputs = (n) => {
const options = [];
for (let i = 0; i < n; i++) {
options.push((
<div className="column" key={i}>
<Select
styles="select"
name={`destination${i}`}
options={(planets && planets[0]) ? planets[0][i] : []}
onSelect={(e) => onDestinationChange(e, i)} />
<Radio
styles="radio"
name={`vehicle${i}`}
options={(vehicles && vehicles[0]) ? vehicles[0][i] : []}
onSelect={(e) => onVehicleChange(e, i)} />
</div>
))
}
return options;
}
const onDestinationChange = (e, index) => {
const selectedName = e.target.value;
const values = Object.values(planets[0]);
let obj = {};
for (let i = 0; i < 4; i++) {
if (i === index) obj[i] = values[i];
else {
obj[i] = values[i].filter((value) => value.name !== selectedName);
}
}
const updatedPlanets = [].concat([obj]);
setPlanets(updatedPlanets);
};
const onVehicleChange = (e) => {
console.log(e.target.value);
};
const onReset = (e) => {
e.preventDefault();
};
return (
< main >
<p className="search-title">Select planets for your Troops to search</p>
<form>
<div className="row">
{ShowInputs(4)}
</div>
<div className="row button-group">
<Link to='/missionreport'>
<Button styles="button" type="submit" text="Find Falcone" />
</Link>
<Button styles="button" type="reset" text="Reset" onPress={onReset} />
</div>
</form>
</main >
);
};
Just remember the index of selected planet in planets list and the index of select element where that planet has been chosen. Then show the selected option only on the select where it's been previously chosen.
That way you must add 2 more fields to your state:
Index of selected planet in planets list (let default be -1 or something negative)
Index of <select> element where it's been chosen (in your case it's the iterator i of the for loop in ShowInputs()).
Then update that fields every time when the user selects something (using onDestinationChange()).