DEV Community

Cover image for Creating A Memory Game With React
Shuvo
Shuvo

Posted on

Creating A Memory Game With React

In this article I will explain how I created this Memory Game with React JS
React.JS card game
Basically what happens in this game is You will have some cards and when you click on them the card flips and reveals the image that it has. We have to try to find cards in pair(cards with same image).

I have a step by step tutorial for this project on my YouTube channel you can check it out.

But if you just want a overview stick around.

Okay so first I created a Cards.js components. That will render all the cards.

// components/Cards.js
import Card from './Card'

function Cards(){
    // Items is a array of objects
    // Each object has id, two objects have same id. We will use them to check if the user have found a pair
    // They also have a img property. I have put all the images in public/img folder
    // Thier stat proprty indicates in what status/state the card is currently on
    // stat == "" means the card is not flipped so the image is hidden
    // stat == "active" means the card is flipped so the image is visible
    // stat == "correct" means we found a pair so the card will be flipped and highlighted in green color
    // stat == "wrong" means we choose 2 different type of cards so the card will be flipped and highlighted in red color
    const [items, setItems] = useState([
        { id: 1, img: '/img/html.png', stat: "" },
        { id: 1, img: '/img/html.png', stat: "" },
        { id: 2, img: '/img/css.png', stat: "" },
        { id: 2, img: '/img/css.png', stat: "" },
        { id: 3, img: '/img/js.png', stat: "" },
        { id: 3, img: '/img/js.png', stat: "" },
        { id: 4, img: '/img/scss.png', stat: "" },
        { id: 4, img: '/img/scss.png', stat: "" },
        { id: 5, img: '/img/react.png', stat: "" },
        { id: 5, img: '/img/react.png', stat: "" },
        { id: 6, img: '/img/vue.png', stat: "" },
        { id: 6, img: '/img/vue.png', stat: "" },
        { id: 7, img: '/img/angular.png', stat: "" },
        { id: 7, img: '/img/angular.png', stat: "" },
        { id: 8, img: '/img/nodejs.png', stat: "" },
        { id: 8, img: '/img/nodejs.png', stat: "" }
    ].sort(() => Math.random() - 0.5)) // shuffling the array using the sort method

    // This function will be called when the user click on a card
    function handleClick(id){
        // just for testing
        alert(id)
    }

    return (
        <div className="container">
            { items.map((item, index) => (
                <Card key={index} item={item} id={index} handleClick={handleClick} />
            )) }
        </div>
    )
}

export default Cards
Enter fullscreen mode Exit fullscreen mode

Then I created the Card.js component.

// components/Card.js
function Card({item, id, handleClick}){
    // geting classes based on status of the card
    const itemClass = item.stat ? " active " + item.stat : ""

    return (
        <div className={"card" + itemClass} onClick={() => handleClick(id)}>
            <img src={item.img} alt="" />
        </div>
    )
}

export default Card
Enter fullscreen mode Exit fullscreen mode

And finally I added the JavaScript game logic in Cards.js component

// components/Cards.js
import { useState } from 'react'
import Card from './Card'

function Cards(){
    const [items, setItems] = useState([
        { id: 1, img: '/img/html.png', stat: "" },
        { id: 1, img: '/img/html.png', stat: "" },
        { id: 2, img: '/img/css.png', stat: "" },
        { id: 2, img: '/img/css.png', stat: "" },
        { id: 3, img: '/img/js.png', stat: "" },
        { id: 3, img: '/img/js.png', stat: "" },
        { id: 4, img: '/img/scss.png', stat: "" },
        { id: 4, img: '/img/scss.png', stat: "" },
        { id: 5, img: '/img/react.png', stat: "" },
        { id: 5, img: '/img/react.png', stat: "" },
        { id: 6, img: '/img/vue.png', stat: "" },
        { id: 6, img: '/img/vue.png', stat: "" },
        { id: 7, img: '/img/angular.png', stat: "" },
        { id: 7, img: '/img/angular.png', stat: "" },
        { id: 8, img: '/img/nodejs.png', stat: "" },
        { id: 8, img: '/img/nodejs.png', stat: "" }
    ].sort(() => Math.random() - 0.5))

    // prev is short for previous
    // prev will store the id of previously clicked card
    const [prev, setPrev] = useState(-1)

    function check(current){
        if(items[current].id == items[prev].id){
            // if previously clicked card is same as current card
            // set the stat of both cards to correct
            items[current].stat = "correct"
            items[prev].stat = "correct"
            setItems([...items])
            setPrev(-1)
        }else{
            // if previously clicked card is same not as current card
            // set the stat of both cards to wrong
            items[current].stat = "wrong"
            items[prev].stat = "wrong"
            setItems([...items])

            // After 1 second, set the stat of both cards to ""
            // So they would flip back
            setTimeout(() => {
                items[current].stat = ""
                items[prev].stat = ""
                setItems([...items])
                setPrev(-1)
            }, 1000)
        }
    }

    function handleClick(id){
        if(prev === -1){ // if prev is -1, then it means no card was clicked previously.
            items[id].stat = "active"
            setItems([...items])
            setPrev(id)
        }else{
            check(id)
        }
    }

    return (
        <div className="container">
            { items.map((item, index) => (
                <Card key={index} item={item} id={index} handleClick={handleClick} />
            )) }
        </div>
    )
}

export default Cards
Enter fullscreen mode Exit fullscreen mode

you can find the complete coded here.

Make sure you checkout my other articles and YouTube channel

0shuvo0 image

Was it helpful? Support me on Patreon

Patreon Logo

Discussion (1)

Collapse
lukeshiru profile image
LUKESHIRU

A few things I noticed:

  • In Card you shouldn't need id and handleClick. The parent should already know the id, so sending it down just to send it back up again is kinda pointless.
  • You should use button instead of div.
  • Having an empty string as an alt for an image to please the a11y linter is not a good practice, you should put some description there.
  • The array of images could be moved out of the component and simplified to avoid duplication.
  • If you can write prev and a comment explaining it means previous, you can just write previous. Same applies for stat that is one letter away from state, or 2 away from status, either way you're not saving time 🤣
  • When you do items[value].stat you're doing a mutation, and you should avoid those. If you really want to do it that way, you should use tools such as immer.
  • You shouldn't use index as a key, instead you should use something that identifies the item (like an id).

Here's a CodeSandbox with this fixes applied to it:

Cheers!