Search code examples
javascripthtmlreactjsuse-effectonkeydown

useEffect EventListener problem REACT HOOKS


I was trying to make a simple Snake-Game with REACT. Everything was good till I needed to use useEffect to move "snake" when keydown fire. When I try to put moveSnake() into useEffect it's giving me some error. When I move it and call it outside effect it's making an infinity loop. I'm using functional components. App.js is messed around because I was stressed out because it's not working and tried every single option. Hope you will get everything.

https://github.com/Pijano97/snake-game code is here, thanks. Also if someone can not access that, here is the code. The snake component just renders snakeDots with a map. Food component just creating random food on the map.

import { useEffect, useState } from "react";
import "./App.css";
import Snake from "./Snake";
import Food from "./Food";

function App() {
    const getRandomFoodDot = () => {
        let min = 1;
        let max = 98;

        let x = Math.floor((Math.random() * (max - min + 1) + min) / 2) * 2;
        let y = Math.floor((Math.random() * (max - min + 1) + min) / 2) * 2;

        return [x, y];
    };

    const [snakeDots, setSnakeDots] = useState([
        [0, 0],
        [2, 0],
    ]);
    const [foodDots, setFoodDots] = useState(getRandomFoodDot());
    const [direction, setDirection] = useState("");

    const moveSnake = () => {
        let dots = [...snakeDots];
        let head = dots[dots.length - 1];

        switch (direction) {
            case "UP":
                head = [head[0], head[1] - 2];
                break;
            case "DOWN":
                head = [head[0], head[1] + 2];
                break;
            case "LEFT":
                head = [head[0] - 2, head[1]];
                break;
            case "RIGHT":
                head = [head[0] + 2, head[1]];
                break;
            default:
                break;
        }
        // adding new head
        dots.push(head);
        // removing last dot
        dots.shift();
        setSnakeDots(dots);
    };

    moveSnake();

    useEffect(() => {
        const keyPressHandler = (e) => {
            switch (e.keyCode) {
                case 38:
                    setDirection("UP");
                    break;
                case 40:
                    setDirection("DOWN");
                    break;
                case 37:
                    setDirection("LEFT");
                    break;
                case 39:
                    setDirection("RIGHT");
                    break;
                default:
                    setDirection("");
                    break;
            }
        };
        document.addEventListener("keydown", keyPressHandler);
        return () => {
            document.removeEventListener("keydown", keyPressHandler);
        };
    }, []);

    console.log(direction);

    return (
        <div className="app">
            <div className="snake">
                <Snake snakeDots={snakeDots} />
                <Food foodDots={foodDots} />
            </div>
        </div>
    );
}

export default App;


Solution

  • I am not sure if I got it correct, but probably you can try this:

    import { useEffect, useState } from "react";
    import "./App.css";
    import Snake from "./Snake";
    import Food from "./Food";
    
    const getDirection = e => {
        switch (e.keyCode) {
            case 38:
                return 'UP';
            case 40:
                return 'DOWN';
            case 37:
                return 'LEFT';
            case 39:
                return 'RIGHT';
            default:
                return '';
        }
    }
    
    function App() {
        const getRandomFoodDot = () => {
            let min = 1;
            let max = 98;
    
            let x = Math.floor((Math.random() * (max - min + 1) + min) / 2) * 2;
            let y = Math.floor((Math.random() * (max - min + 1) + min) / 2) * 2;
    
            return [x, y];
        };
    
        const [snakeDots, setSnakeDots] = useState([
            [0, 0],
            [2, 0],
        ]);
        const [foodDots, setFoodDots] = useState(getRandomFoodDot());
        const [direction, setDirection] = useState({val: ''});
    
        useEffect(() => {
            let dots = [...snakeDots];
            let head = dots[dots.length - 1];
    
            switch (direction.val) {
                case "UP":
                    head = [head[0], head[1] - 2];
                    break;
                case "DOWN":
                    head = [head[0], head[1] + 2];
                    break;
                case "LEFT":
                    head = [head[0] - 2, head[1]];
                    break;
                case "RIGHT":
                    head = [head[0] + 2, head[1]];
                    break;
                default:
                    break;
            }
            // adding new head
            dots.push(head);
            // removing last dot
            dots.shift();
            setSnakeDots(dots);
        }, [direction]);
    
        useEffect(() => {
            const keyPressHandler = (e) => {
                setDirection({ val: getDirection(e) });
            };
            document.addEventListener("keydown", keyPressHandler);
            return () => {
                document.removeEventListener("keydown", keyPressHandler);
            };
        }, []);
    
        console.log(direction);
    
        return (
            <div className="app">
                <div className="snake">
                    <Snake snakeDots={snakeDots} />
                    <Food foodDots={foodDots} />
                </div>
            </div>
        );
    }
    
    export default App;
    

    I just created getDirection func for getting direction from a key event and added one more useEffect with logic from the moveSnake function I didn't know the current functionality of your project, but the code above works, and the shake can be moved.