@christopher-adolphe
Posted
Hi @Dave š,
You did a great job on this challenge. š I had a great time playing the game too. š
I saw you question about the issues you are having with dependency array of React's useEffect
hook. I had a quick look at your code for the Board.js
component and here are a few things that I have noticed which might be why you are facing those issues. I'll give you some rules/guidelines that I use myself when I work with the useEffect
hook and I'll try to keep it simple so that you'll know where to go from here.
Rule 1: Don't ignore the warnings about the dependency array
- We get warnings when React sees we are doing something wrong. You should take a step back and carefully review your implementation of the
useEffect
hook.
Rule 2: Know the data types of the dependencies being passed to the array
- This is where most trouble come from. We should carefully check the data types of the different elements being passed to the dependency array. If the dependencies are primitive data types (i.e: string, number, boolean, null, undefined) we don't have much to worry as they are compared by value. But if they are reference data types (i.e array, object, function, date), the
useEffect
hook will compare them by reference (i.e their pointer in memory) and each time the component renders, we create a new reference to those dependencies and as theuseEffect
hook detects they are different, it reruns hence resulting in an infinite loop. š± Hopefully, React provides us with tools to work around this problem; they are theuseMemo
anduseCallback
hooks. Consider theuseEffect
below which was extracted from yourBoard.js
component:
React.useEffect(() => {
if (gameType !== "CPU") return;
if (marker !== turn) {
renderSquareChoice(computerMove(squares, marker));
}
}, [gameType, turn]);
renderSquareChoice
is a function inside your component and you are using it inside useEffect
, so React prompts you that it needs to be added to the dependency array. Now, you add it as a dependency, you get an infinite loop because renderSquareChoice
is a function meaning it is a reference data type. To fix this, you need to wrap the renderSquareChoice
with useCallback
like so:
const renderSquareChoice = useCallback((square) => {
if (status || squares[square]) return;
const nextSquares = [...squares];
nextSquares[square] = turn;
setSquares(nextSquares);
}, []);
And you will be able to add it to useEffect
as a dependency without causing an infinite loop. NOTE: In the above code, useCallBack
also has a dependency array, you might need to add status
and squares
as dependencies.
- There are cases where we don't need to pass the entire object or array as a dependency to
useEffect
. For example, if my component has aperson
state/prop but my effect is only dependent on theage
property of thatperson
state. All we need to do is to pass thisage
property touseEffect
like below:
const [ person, setPerson ] = useState({
name: 'Chris',
age: 36,
role: 'guest'
});
useEffect(() => {
// some side effect that needs to run whenever the age of `person` state is mutated
}, [person.age]);
So we are not passing the entire person
state which an object and therefore a reference data type because each render of the component would create a new reference for the person
state. Instead, we pass only the age
property which is a number and therefore a primitive data type. I illustrated a simple case here but for complex cases the useMemo
hook would be more appropriate. This feedback is already very long, I suggest you to read more about useMemo
.
Rule 3: Use the functional variant of the state setter function inside useEffect
- Whenever
useEffect
is dependent on a state which is mutated inside it, you should use the functional variant of the state setter function. Taking another example extracted from theBoard.js
component:
// Update total scores
React.useEffect(() => {
if (status === null) return;
const newScore = { ...score };
newScore[status] += 1;
setScore(newScore);
}, [status]);
This useEffect
should be refactored as follows:
// Update total scores
React.useEffect(() => {
if (status === null) return;
setScore((prevScore) => {
const newScore = { ...prevScore };
newScore[status] += 1;
return newScore;
});
}, [status]);
By doing so, our useEffect
is no more dependent on score
state and hence it isn't needed in the dependency array.
I know that's a lot to read as feedback but I tried to keep it as lean as possible. useEffect
is a big topic in itself. I also got these issues while tackling the Coffeeroasters challenge with React (still in progress š). The more you practice, the better you'll get at it.
I hope this helps you as a starting point.
Here are some resources from Jack Herrington that helped me:
Keep it up.
Marked as helpful
@christopher-adolphe wow thanks so much for taking the time to write such a comprehensive response.
I had learnt a little bit about useMemo and useCallback, but couldn't quite see how to apply them to my code. You have made this super clear and I'll be going through everything you wrote again in much more detail and doing a refactor.
I'll check out those resources too. Thanks once again for the feedback it is really, really appreciated!!
@christopher-adolphe
Posted
@dwhenson you are welcome.
I'm happy to help and glad to see this was helpful to you. š
See you on the next one.
Best regards.