I've been making a game which at the end, requires the user to type their guess. To avoid confusion in my actual project, I created something in codesandbox which demonstrates the problem I'm having. I should add that the game in codesandbox isn't suppose to make much sense. But essentially you just click any box 5 times which generates a random number and when the component mounts, it also creates an array with 5 random number. At the end, you type a number and it checks if both arrays contain the key entered and colors them accordingly. The problem I'm having is that once the guess component is shown, all the hooks states return to their initial states.
Main.tsx
import { Guess } from "./Guess";
import { useHook } from "./Hook";
import { Loading } from "./Loading";
import "./styles.css";
export const Main = () => {
const {loading, count, handleClick, randArr} = useHook()
return (
<div className="main">
{!loading && count < 5 &&
<div className='click-container'>
{Array.from({length: 5}).fill('').map((_, i: number) =>
<div onClick={handleClick} className='box' key={i}>Click</div>
)}
</div>
}
{loading && <Loading count={count} />}
{!loading && count >= 5 && <Guess arr={randArr} />}
</div>
);
}
Hook.tsx
import { useEffect, useState } from 'react'
export const useHook = () => {
type guessType = {
keyNum: number
isContain: boolean
}
const [disable, setDisable] = useState(true)
const [randArr, setRandArr] = useState<number[]>([])
const [initialArr, setInitialArr] = useState<number[]>([])
const [count, setCount] = useState<number>(0)
const [loading, setLoading] = useState(true)
const [guess, setGuess] = useState<guessType[]>([])
const randomNum = () => {
return Math.floor(Math.random() * (9 - 0 1) 0);
}
useEffect(() => {
const handleInitialArr = () => {
for (let i = 0; i < 5; i ) {
let num = randomNum()
setInitialArr((prev) => [...prev, num])
}
}
handleInitialArr()
}, [])
const handleClick = () => {
if (!disable) {
let num = randomNum()
setRandArr((prev)=> [...prev, num])
setCount((prev) => prev 1)
setDisable(true)
setLoading(true)
}
}
useEffect(()=> {
const handleLoading = () => {
setTimeout(() => {
setLoading(false)
}, 500)
}
const handleRound = () => {
setDisable(false)
}
handleLoading()
handleRound()
}, [count])
const handleKeyUp = ({key}) => {
const isNumber = /^[0-9]$/i.test(key)
if (isNumber) {
if (randArr.includes(key) && initialArr.includes(key)) {
setGuess((prev) => [...prev, {keyNum: key, isContain: true}])
console.log(' they both have this number')
} else {
setGuess((prev) => [...prev, {keyNum: key, isContain: false}])
console.log(' they both do not contain this number ')
}
}
}
console.log(count)
console.log(randArr, ' this is rand arr')
console.log(initialArr, ' this is initial arr')
return {
count,
loading,
handleClick,
randArr,
handleKeyUp,
guess
}
}
Guess.tsx
import React, { useEffect } from "react";
import { useHook } from "./Hook";
import "./styles.css";
type props = {
arr: number[];
};
export const Guess: React.FC<props> = (props) => {
const { handleKeyUp, guess } = useHook();
useEffect(() => {
window.addEventListener("keyup", handleKeyUp);
return () => {
window.removeEventListener("keyup", handleKeyUp);
};
}, [handleKeyUp]);
console.log(props.arr, " this is props arr ");
return (
<div className="content">
<div>
<p>Guesses: </p>
<div className="guess-list">
{guess.map((item: any, i: number) =>
<p key={i} className={guess[i].isContain ? 'guess-num-true': 'guess-num-false'} >{item.keyNum}</p>
)}
</div>
</div>
</div>
);
};
Also, here is the codesandbox if you want to take a look for yourself: https://codesandbox.io/s/guess-numbers-70fss9
Any help would be deeply appreciated!!!
CodePudding user response:
Fixed demo: https://codesandbox.io/s/guess-numbers-fixed-kz3qmw?file=/src/my-context.tsx:1582-2047
You're under the misconception that hooks share state across components. The hook will have a new state for every call of useHook()
. To share state you need to use a Context
.
type guessType = {
keyNum: number;
isContain: boolean;
};
type MyContextType = {
count: number;
loading: boolean;
handleClick: () => void;
randArr: number[];
handleKeyUp: ({ key: string }) => void;
guess: guessType[];
};
export const MyContext = createContext<MyContextType>(null as any);
export const MyContextProvider: FC<PropsWithChildren<{}>> = ({ children }) => {
// Same stuff as your hook goes here
return (
<MyContext.Provider
value={{ count, loading, handleClick, randArr, handleKeyUp, guess }}
>
{children}
</MyContext.Provider>
);
};
export const App = () => {
return (
<div className="App">
<MyContextProvider>
<Page />
</MyContextProvider>
</div>
);
};
export const Main = () => {
const { loading, count, handleClick, randArr } = useContext(MyContext);
...
}
export const Guess: React.FC<props> = (props) => {
const { handleKeyUp, guess } = useContext(MyContext);
...
}
Your handleKeyUp
function is also bugged, a good example of why you need to type your parameters. key
is a string, not a number. So the condition will always be false.
const handleKeyUp = ({ key }: {key: string}) => {
const num = parseInt(key);
if (!isNaN(num)) {
if (randArr.includes(num) && initialArr.includes(num)) {
setGuess((prev) => [...prev, { keyNum: num, isContain: true }]);
console.log(" they both have this number");
} else {
setGuess((prev) => [...prev, { keyNum: num, isContain: false }]);
console.log(" they both do not contain this number ");
}
}
};