Refactoring a memory numbers game in React - javascript

I've been learning React by doing simple games.
I'm doing a memory game which is, for the most part, finished and I'm not sure how I should refactor the code to be better.
The user task is basically to remember the numbers flashed, and then click them, until the user can't hold them in memory anymore.
How should I divide/refactor the code?
Also, how would you go forward with games like this? Would you perhaps create a gameEngine or such? Although this would be the part of refactoring right. I'm using React for games because I am learning React and improving it.
Code:
import React, { useState, useEffect } from 'react';
import Cell from './sub-components/Cell/Cell';
import './styles.scss';
function BoardScene() {
const [gameNumbers, setGameNumbers] = useState([]);
const [isPlayerTurn, setIsPlayerTurn] = useState(false);
const [currentScore, setCurrentScore] = useState(0);
const [bestScore, setBestScore] = useState(0);
const [flashCard, setFlashCard] = useState(null);
const [clickedNumber, setClickedNumber] = useState(null);
const [currentUserIndex, setCurrentUserIndex] = useState(0)
function startGame() {
addNewNumber()
}
function resetGame() {
setGameNumbers([])
setCurrentUserIndex(0)
if (bestScore < gameNumbers.length) return setBestScore(gameNumbers.length)
}
const blinkCell = () => {
let count = 0;
setIsPlayerTurn(false);
const timerID = setInterval(() => {
console.log("Inside interval before if")
console.log("currentUserIndex", count, gameNumbers.length)
if (count === gameNumbers.length) {
setIsPlayerTurn(true);
console.log("Inside time out if statement")
clearInterval(timerID);
count = 0;
setFlashCard(null);
} else {
setFlashCard(gameNumbers[count]);
count++;
}
}, 500);
};
function generateRandomNumber(min, max) {
return Math.floor(Math.random() * (max - min) + min);
}
function addNewNumber() {
console.log("Add new number")
let memoryNumber = generateRandomNumber(1, 9)
setGameNumbers(gameNumbers => [...gameNumbers, memoryNumber])
}
function clickedNumberHandle(number) {
console.log("Clicked number", number)
setClickedNumber(number)
isMatch(number)
}
function isMatch(number) {
if (number === gameNumbers[currentUserIndex]) {
console.log("Correct")
if (currentUserIndex + 1 === gameNumbers.length) {
setCurrentUserIndex(0)
console.log("set current index 0")
addNewNumber()
blinkCell();
} else {
console.log("set current index + 1")
setCurrentUserIndex(currentUserIndex + 1)
}
} else {
resetGame()
console.log("game over")
}
}
useEffect(() => {
blinkCell()
console.log("Use effect start blinkCell")
}, [gameNumbers])
return (
<>
<div className="game-container">
<div className="testing-stuff">
<div>
<button onClick={startGame}>Start Game</button>
</div>
<div onClick={addNewNumber}>
<button>Add new number</button>
</div>
<div>
<span>Game numbers: </span>
{gameNumbers.map((item, i) => {
return <span key={i}>{item}</span>
})}
</div>
<div>
<span>User Turn: {isPlayerTurn ? "True" : "False"}</span>
</div>
<div>
<span>Score: {gameNumbers.length}</span>
</div>
</div>
<div className="board">
{Array(9).fill().map((x, i) => {
return (
<Cell key={i} onClick={() => clickedNumberHandle(i + 1)} number={i + 1} active={i + 1 === flashCard ? true : false} />
)
})}
</div>
<div className="stats">
<div className="stats__score-wrap">
<span>Score: </span><span>{gameNumbers.length}</span>
</div>
<div className="stats__bestScore-wrap">
<span>Best Score: </span><span>{bestScore}</span>
</div>
</div>
</div>
</>
);
}
export default BoardScene;
GitHub Code: https://github.com/AurelianSpodarec/memory-numbers-game/blob/e177dfdaafe5daf393f1ae8fcee0827a16474e8f/src/scenes/BoardScene/BoardScene.js
Live Game: https://memory-numbers-game.netlify.app/

I'm no expert but I would personally re-factor to use ES6 syntax for all your functions. At the moment some of them are using arrow functions and some aren't.
I'd also personally have functions that are performing generic actions you might want to use across your codebase (such as generateRandomNumber) in their own modules.
On a specific point that isn't an opinion. I would not use the index (i) of your array as a key. This can lead to many issues. I would look into using a library like UUID to generate these keys.
Other than that looks again, and again I'm no expert. If you want some more info from someone who is I'd recommend Kent C Dodd blog and Ben Awads Youtube channel.

Related

Cannot read properties of null ( play ) when leaving the component

<script>
import {onMount} from 'svelte'
let phrase = "Head Admin Esports Middle East organization ( es.me website) and Project Manager at Arab Esports Website."
let typedChar = "";
let index = 0;
let aud;
const typing = () => {
setInterval(async () => {
if (index < phrase.length) {
typedChar += phrase[index];
index++;
await aud.play()
} else {
clearInterval()
}
}, 20)
}
onMount(() => {
typing()
})
</script>
<audio bind:this={aud} src={typingAudio} />
Here I am showing the data in typing effect I wanted to add sound to this typing effect it works fine as I wanted but when I leave the component mid typing it shows Cannot read properties of null ( play ) error
This might be because the interval isn't cleared - there's neither an id provided in the call inside typing nor is it cleared when the component is destroyed
clearInterval() - If the parameter provided does not identify a previously established action, this method does nothing.
let interval
const typing = () => {
interval = setInterval(async () => {
if (index < phrase.length) {
typedChar += phrase[index];
index++;
await aud.play()
} else {
clearInterval(interval)
}
}, 20)
}
onMount(() => {
typing()
return () => clearInterval(interval)
})
Alternatively to onDestroy
If a function is returned from onMount, it will be called when the component is unmounted.
(playing the audio might be blocked if the user hasn't yet interacted with the page)

TypeError: Object(...) is not a function when creating a JS object

I am getting a TypeError: Object(...) is not a function compiler issue and I am not sure why, below are the corresponding files - I am specifically getting an issue where
const mySubmission = GetStudentSubmissionWithUrl();
is run. My function GetStudentSubmissionWithUrl() returns a JavaScript object and I want to assign this object to another JavaScript object.
function Details () {
const mySubmission = GetStudentSubmissionWithUrl();
/* TODO: do the filtering of a submission in the backend instead of right here - will cause a big performance hit */
return (
<React.Fragment>
{mySubmission?.images && <ImageCarousel imageList={mySubmission.images}/>}
<div className="card-body">
<h1 className="title-event">{}</h1>
{mySubmission?.subjectName && <h2 className="title-location"> {mySubmission.subjectName}</h2>}
<br></br>
<div className="row=title">
{mySubmission?.location && <h3 className="submitter-location"> {mySubmission.location}</h3>}
{mySubmission?.description && <p> {mySubmission.description}</p>}
</div>
<br></br>
</div>
{mySubmission?.sources && <Source sourceList={mySubmission.sources}/>}
</React.Fragment>
);
}
export default Details;
Here is where my corresponding GetStudentSubmissionWithUrl() is defined:
import React, { useState, useEffect } from 'react';
import { getAllVerified } from '../util';
//return a StudnetSubmission given the current ulr link that we are in
function GetStudentSubmissionWithUrl() {
const [submissions, setSubmissions] = useState(null);
useEffect(() => {
const fetchData = async () => {
const data = await getAllVerified(); //just get the one
setSubmissions(data);
};
fetchData();
}, []);
let str = JSON.stringify(window.location.pathname);
let submissionId = str.split("/").pop();
submissionId = submissionId.replace('"', '');
let mySubmission;
if (submissions != null) {
var arrayLength = submissions.length;
for (var i = 0; i < arrayLength; i++) {
let string_id = submissions[i].id;
string_id = string_id.replace('"', '');
if (submissionId === string_id) {
mySubmission = JSON.parse(JSON.stringify(submissions[i]));
}
}
}
return mySubmission;
}
export default GetStudentSubmissionWithUrl;
Any help would be much appreciated - thanks in advance!

Convert jQuery Animation to React Hook

I am building an animation where the letters of two words appear one by one, similar to a slide-in effect. I have the code made with jQuery, but I need to implement it in my React app (built with hooks). The code that I have takes the text, splits it creating individual letters, and adds spans between those letters. This is the following code that I need to convert to React:
const logoText = document.querySelector('.logo');
const stringText = logoText.textContent;
const splitText = stringText.split("");
for (let i=0; i < splitText.length; i++) {
text.innerHTML += "<span>" + splitText + "</span>"
}
let char = 0;
let timer = setInterval(onTick, 50)
I was wondering if you guys could help me figure it out. Thanks a lot!
You need to iterate over the text and create a timeout function for every letter with a different time of execution, that way will be visible the slide effect you are expecting:
Custom hook
const useSlideInText = text => {
const [slide, setSlide] = useState([]);
useEffect(() => {
Array.from(text).forEach((char, index) => {
const timeout = setTimeout(
() =>
setSlide(prev => (
<>
{prev}
<span>{char}</span>
</>
)),
index * 100
);
});
}, []);
return slide;
};
Usage
function App() {
const slide = useSlideInText("hello");
return (
<div>
{slide}
</div>
);
}
Working example
I am assuming the React components that you want to run this hook in possess the text you want to split. I am also assuming that on the interval, you want to reveal more of the text. In that case my example solution would look like this:
Hook
import {useState, useEffect} from "react";
const useSlideInText = (text) => {
const [revealed, setRevealed] = useState(0);
useEffect(() => {
if (revealed < text.length) {
setTimeout(() => setRevealed(revealed + 1), 50);
}
});
return text.split('').slice(0, revealed).map((char) => (<span>{char}</span>));
}
Example usage
const MyComponent = (props) => {
const displayText = useSlideInText(props.text);
return <div>{displayText}</div>;
};
going off of the other answer:
const generateDisplayTest = (text, numChars) => text.split('').slice(0, numChars).map((char) => (<span>{char}</span>));
const MyComponent = (props) => {
const [revealed, setRevealed] = useState(0);
useEffect(() => {
if (revealed < props.text.length) {
setTimeout(() => setRevealed(revealed + 1), 50);
}
}, [revealed]);
const displayText = generateDisplayTest(props.text, revealed);
return <div>{displayText}</div>;
};
including [revealed] in the useEffect means that useEffect will run every time that revealed changes. Also I always feel that useState/useEffect should live on the component, it has been that way in the place I worked but I'm not sure if that is industry standard.

Slider Component not working properly in React

I'm trying to build a component where an array of values are presented like a slider, where every time, the old content gets replaced by a new one, and that can be done using the buttons next and previous. The component is working, but I'm struggling a little bit in the edge cases, where I have to disable the buttons
I'll leave the link to a codesandbox where the component is being built, I'm sure it'll be easier to understand what's going on.
Link to sandbox
Try not to use state value for tracking button disabled status. Please check below.
import React, { useState } from "react";
export default function App() {
const data = ["q", "c", "s", "a"];
const [iterator, setIterator] = useState(0);
const [curr, setCurr] = useState(data[iterator]);
const fetchNext = () => {
if (iterator === data.length - 1) {
return;
}
setIterator((prev) => prev + 1);
setCurr(data[iterator + 1]);
};
const fetchPrevious = () => {
if (iterator === 0) {
return;
}
setIterator((prev) => prev - 1);
setCurr(data[iterator - 1]);
};
const nextDisabled = iterator >= data.length - 1;
const prevDisabled = iterator <= 0;
return (
<div>
<h1>{curr}</h1>
<button disabled={nextDisabled} onClick={fetchNext}>
next
</button>
<button disabled={prevDisabled} onClick={fetchPrevious}>
previous
</button>
</div>
);
}

Stop function while running in React

Introduction
I am building a sorting algorithms visualizer for studying purposes, and i have a sortArray() function that takes care of animating the array being sorted. As you can see in the code.
and it works fine, but the problem is that i wanted to give the array takes some time to get sorted, and i wanted a way to stop this function while running.
What I've Tried So Far
After i realised that i had this problem, i thought that a "Stop" button would be a good way to solve my problem i added a new state called "Abort", and the original idea was that while the sortArray() function is running, the function would check everytime if the abort state is set to true, and if it is, the function would stop. and of course, it didn't work.
after that my previous attempt had failed, i deleted the "abort" state from the code and transformed it into a normal variable, thinking that the problem maybe was related to the setState function being async, and it didn't work either.
after that i did some research, but i didn't found anything useful.
The Code:
resetArray(){
const arrayToSort = [];
const prevChanged = [];
for (let i = 0; i < this.state.numberOfItems; i++) {
arrayToSort.push(this.RandomIntBetweenRange(5, 1000));
}
this.setState({ arrayToSort, prevChanged, abort: false });
}
generateNewArray(){
this.setState({abort: true},{
this.resetArray();
});
}
async SortArray(algo){
let sortedArrayAnim = algo(this.state.arrayToSort);
let arrayToSort = this.state.arrayToSort;
let prevChanged = this.state.prevChanged;
for (let index = 0; index < sortedArrayAnim.length; index++) {
const [i,j] = sortedArrayAnim[index];
if(this.state.abort){
console.log(abort);
return null;
}
let temp = arrayToSort[i];
arrayToSort[i] = arrayToSort[j];
arrayToSort[j] = temp;
prevChanged.push(i,j);
if(index == sortedArrayAnim.length - 1){
prevChanged.push(arrayToSort.length + 1, arrayToSort.length + 1);
this.setState({prevChanged});
}
this.setState({ arrayToSort,prevChanged });
await sleep(10);
}
}
render() {
const {arrayToSort} = this.state;
return (
<div className="main-div">
{arrayToSort.map((value, idx) => (
<div className="array-item" key={idx} style={{height: value, width: 800 / this.state.numberOfItems, backgroundColor: this.getColor(idx)}}>
</div>
))}
<button onClick={() => this.generateNewArray()}>Generate new array</button>
<button onClick={() => this.SortArray(BubbleSort)}>Bubble Sort</button>
<button onClick={() => this.SortArray(InsertionSort)}>Insertion Sort</button>
<button onClick={() => this.SortArray(QuickSort)}>Quick Sort</button>
<input type="number" min="5" max="1500" onChange={(event) => this.handleOnChange(event)} defaultValue={this.state.numberOfItems}/>
</ div>
);
}
Could you do something involving a Web-worker?
Structure the class something like this
class LongOperation extends React.Component {
constructor(props) {
super(props)
this.state = {
running: false,
results: null,
worker: null,
}
}
startLongOp(){
const worker = new Worker('yourIsolatedOp.js')
//...setup
this.setState({
running: true,
worker: worker,
})
}
cancel() {
if(running) {
this.state.worker.terminate()
}
this.setState({
running: false,
worker: null,
})
}
render() {
//Do your rendering
<button onClick={e => this.startLongOp()}>Start</button>
<button onClick={e => this.cancel()}>Cancel</button>
}
}
//while loop:
while(this.state.running) {
//do stuff
}
You could also probable do it with a while loop, but it would be on the UI thread. I have not tried this, it is just a sketch from the top of my head. Now designing a concurrent sort is going to be the bigger challenge... Workers are going to be the easiest if you can isolate it.
*note: I didn't go into details on how to deal with the web-worker passing data etc.. but you can easily find it. I see no reason you can't update the state from these messages.

Categories