Skip to content
  • Unlock Pro
  • Log in with GitHub
Solution
Submitted almost 3 years ago

Tic Tac Toe - React and Styled Components

react, styled-components
Dave•5,295
@dwhenson
A solution to the Tic Tac Toe game challenge
View live sitePreview (opens in new tab)View codeCode (opens in new tab)

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

Code
Select a file

Please log in to post a comment

Log in with GitHub

Community feedback

  • P
    Christopher Adolphe•620
    @christopher-adolphe
    Posted almost 3 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 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 the useEffect 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 the useMemo and useCallback hooks. Consider the useEffect below which was extracted from your Board.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 a person state/prop but my effect is only dependent on the age property of that person state. All we need to do is to pass this age property to useEffect 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 the Board.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:

    • Mastering React's useEffect
    • 5 Pro-Level React Do's & Don'ts
    • Common React Mistakes: useEffect

    Keep it up.

    Marked as helpful
  • P
    Miran Legin•740
    @miranlegin
    Posted almost 3 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:

    1. 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
    2. 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
    3. 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 helpful

Join 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

Stay up to datewith new challenges, featured solutions, selected articles, and our latest news

Frontend Mentor

  • Unlock Pro
  • Contact us
  • FAQs
  • Become a partner

Explore

  • Learning paths
  • Challenges
  • Solutions
  • Articles

Community

  • Discord
  • Guidelines

For companies

  • Hire developers
  • Train developers
© Frontend Mentor 2019 - 2025
  • Terms
  • Cookie Policy
  • Privacy Policy
  • License

Oops! 😬

You need to be logged in before you can do that.

Log in with GitHub

Oops! 😬

You need to be logged in before you can do that.

Log in with GitHub

How does the accessibility report work?

When a solution is submitted, we use axe-core to run an automated audit of your code.

This picks out common accessibility issues like not using semantic HTML and not having proper heading hierarchies, among others.

This automated audit is fairly surface level, so we encourage to you review the project and code in more detail with accessibility best practices in mind.

How does the CSS report work?

When a solution is submitted, we use stylelint to run an automated check on the CSS code.

We've added some of our own linting rules based on recommended best practices. These rules are prefixed with frontend-mentor/ which you'll see at the top of each issue in the report.

The report will audit all CSS, SCSS and Less files in your repository.

How does the HTML validation report work?

When a solution is submitted, we use html-validate to run an automated check on the HTML code.

The report picks out common HTML issues such as not using headings within section elements and incorrect nesting of elements, among others.

Note that the report can pick up “invalid” attributes, which some frameworks automatically add to the HTML. These attributes are crucial for how the frameworks function, although they’re technically not valid HTML. As such, some projects can show up with many HTML validation errors, which are benign and are a necessary part of the framework.

How does the JavaScript validation report work?

When a solution is submitted, we use eslint to run an automated check on the JavaScript code.

The report picks out common JavaScript issues such as not using semicolons and using var instead of let or const, among others.

The report will audit all JS and JSX files in your repository. We currently do not support Typescript or other frontend frameworks.

Oops! 😬

You need to be logged in before you can do that.

Log in with GitHub

Oops! 😬

You need to be logged in before you can do that.

Log in with GitHub