Tic Tac Toe - React and Styled Components
Design comparison
Solution retrospective
This is my second project using React and first using styled components. I put some details in the readme, but some issues/questions I have are:
- One issue I had with styled components is keeping track of whether the components in the file were now React components or styled components! I also struggled mapping the styled components to the rendered DOM in the dev tools, which made debugging a bit tricky in some cases. How to people manage this?
- 'React.useEffect` is asking for additional dependencies in a couple of places in board.js (see FIXMEs). The only way I could get the app deployed was to ignore them. If I add them as suggested I end up with infinite loops. I wasn't sure how to deal with this if any one knows how to deal with this I would love to know.
- Board.js is a bit of a monster, I had a look at refactoring things, but I couldn't see any obvious ways to do this. I considered moving the remaining game logic functions out, but they all change state so this didn't seem sensible. Any suggestions would be welcome!
Thanks in advance for any suggestions and feedback. Cheers Dave
Community feedback
- @christopher-adolphePosted about 2 years ago
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 theBoard.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 theuseEffect
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 insideuseEffect
, 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 becauserenderSquareChoice
is a function meaning it is a reference data type. To fix this, you need to wrap therenderSquareChoice
withuseCallback
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 addstatus
andsquares
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 theperson
state. Instead, we pass only theage
property which is a number and therefore a primitive data type. I illustrated a simple case here but for complex cases theuseMemo
hook would be more appropriate. This feedback is already very long, I suggest you to read more aboutuseMemo
.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 onscore
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 helpful1@dwhensonPosted about 2 years ago@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!!
0@christopher-adolphePosted about 2 years ago@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.
1 - We get warnings when React sees we are doing something wrong. You should take a step back and carefully review your implementation of the
- @miranleginPosted about 2 years ago
Hi Dave, congratulations on completing this challenge!
I'm no expert in React by any means, even struggle in Javascript a bit so can't comment on that part. So I will comment on some things that I can see and think they can slightly improve user experience.
I knew when i saw the notification that you completed another challenge that i won't be disappointed and you prove me right again. I think you did an awesome job on this one. Everything is working like expected, there were no hiccups or anything strange to happen. I tried it on a 27" Desktop and a 6" Smartphone. Restarting browser to continue where you left of is working like expected.
So let's get started with some of the minor improvements in my opinion:
- when you are playing with the mouse i would love the see the
cursor:pointer
on the buttons. It is such a minor thing but in my opinion it is definitely needed here - on the intro screen where you are choosing your opponent i would love to see marginally taller buttons (CPU vs PLAYER), it is slightly awkward to hit it on the smartphone device
- when your opponent is CPU just some delay to give impression that CPU is calculating his next move, to be more immersed in the experience of a game
Looking forward to see you next challenges!
Cheers, Miran
Marked as helpful1@dwhensonPosted about 2 years ago@miranlegin thanks so much for the feedback - and kind words! - it is much appreciated.
I think I've addressed the issues you suggested I deal with above. The first two were pretty simple, the last was a bit tricky, but I think I managed. I made it a bit random too so it seems less mechanical.
Do let me know if you spot anything else. I'm new to React so it's helpful to poke around things and try and work out what's going on!
Thanks again Dave
0 - when you are playing with the mouse i would love the see the
Please log in to post a comment
Log in with GitHubJoin our Discord community
Join thousands of Frontend Mentor community members taking the challenges, sharing resources, helping each other, and chatting about all things front-end!
Join our Discord