Search code examples
javascriptreactjspath-finding

Node by node animation is not rendered


I am trying to implement Clement's Pathfinding visualizer by watching his Youtube tutorial. However, my node by node animation is not getting rendered even if I provide the delay for each node animation. Can you please take a look at the code?

The output I want is the node by node animation

Also, I am new to React JS and a beginner at JavaScript. Although I understand what is going on, on the surface level, it is a bit difficult to make changes like this. Can you also suggest what can I do to strengthen my fundamentals.

Thank you

import React, {Component} from "react";
import Node from './Node/Node';
import './Node/Node.css'

import './PathfindingVisualizer.css';
//import { render } from "@testing-library/react";
import {dijkstra_algorithm as dijkstra} from '../algorithms/dijkstra'

const START_NODE_ROW = 10;
const START_NODE_COL = 15;
const FINISH_NODE_ROW = 15; 
const FINISH_NODE_COL = 30;

export default class PathfindingVisualizer extends Component{
    constructor(){
        super();
        this.state={
        grid: [],
        mouseIsPressed: false
        };
    }

    componentDidMount() {
        const grid= getInitialGrid();
        this.setState({grid});
    }

    animateDijkstra(visitedNodesInOrder) {
        visitedNodesInOrder.forEach((node, i) => {
          setTimeout(() => {
            this.setState((prevState) => {
              const newGrid = prevState.grid.slice();
              const newNode = {
                ...node,
                isVisited: true,
              };
              newGrid[node.row][node.col] = newNode;
              return { grid: newGrid };
            });
          }, 1000 * i); // Delay each animation by 1000 milliseconds (1 second)
        });
      }
      
      
      
      

    visualizeDijkstra(){
        const {grid} = this.state;
        const startNode=grid[START_NODE_ROW][START_NODE_COL];
        const finishNode=grid[FINISH_NODE_ROW][FINISH_NODE_COL];
        const visitedNodesInOrder=dijkstra(grid, startNode,finishNode);
        this.animateDijkstra(visitedNodesInOrder);
    }

    /*handleMouseEnter(row, col) {
        if (!this.state.mouseIsPressed) return;
        const newGrid = getNewGridWithWallToggled(this.state.grid, row, col);
        this.setState({grid: newGrid});
    }
    
    handleMouseUp() {
    this.setState({mouseIsPressed: false});
    }*/

    render(){
        const {grid}=this.state;

        return(
            <>
                <button onClick={() => this.visualizeDijkstra()}>
                    Visualize Dijkstra Algorithm
                </button>
            <div className="grid">
                {grid.map((row,rowIdx)=>{
                    return(
                        <div key={rowIdx}>
                            {row.map((node,nodeIdx) => {
                            const {isStart, isFinish, isVisited} = node;
                            return(
                                
                                <Node
                                    key={nodeIdx}
                                    isStart={isStart}
                                    isFinish={isFinish}
                                    isVisited={isVisited}
                                    >

                                </Node>
                            );
                        })}
                        </div>
                    );
                    })}
                
            </div>
        </>
        );
    }
}
const getInitialGrid = () => {
    const grid=[];
   
    for(let row=0; row<20; row++){
        const currentRow=[];
        for(let col=0; col<50; col++){
            currentRow.push(createNode(col,row));
        }
            
            grid.push(currentRow);
        }
    return grid;
};

const createNode = (col, row) => {
    return{
        col,
        row,
        isStart: row === START_NODE_ROW && col === START_NODE_COL,
        isFinish: row === FINISH_NODE_ROW && col ===FINISH_NODE_COL,
        distance: Infinity,
        isVisited: false,
        //isWall: false,
        previousNode: null,
    };
};

This is my output in one go.

enter image description here


Solution

  • The problem is that you are changing the grid while doing the calculations and stuff... and when you click to start rendering, the grid is already all set. I prevented that by having a copy of the empty grid in the state and loop through this empty one (copy using structuredClone to do a deep copy, if you copy by spreading ... or .slice() it would make a shallow copy and the same problem would happen)

    https://codesandbox.io/s/spring-star-sgn2s5?file=/src/PathfindingVisualizer/PathfindingVisualizer.jsx

    PathfindingVisualizer.jsx

    import React, { Component } from "react";
    import Node from "./Node/Node";
    import "./Node/Node.css";
    
    import "./PathfindingVisualizer.css";
    import { dijkstra_algorithm as dijkstra } from "../algorithms/dijkstra";
    
    const START_NODE_ROW = 10;
    const START_NODE_COL = 15;
    const FINISH_NODE_ROW = 15;
    const FINISH_NODE_COL = 30;
    
    export default class PathfindingVisualizer extends Component {
      constructor() {
        super();
        this.state = {
          grid: [],
          animatedGrid: [], // 🟥 new state to have an empty grid
          mouseIsPressed: false
        };
      }
    
      componentDidMount() {
        const grid = getInitialGrid();
        this.setState({
          grid,
          animatedGrid: structuredClone(grid) // 🟥 passing a deepcopy of the grid
        });
      }
    
      animateDijkstra(visitedNodesInOrder) {
        visitedNodesInOrder.forEach((node, i) => {
          setTimeout(() => {
            this.setState((prevState) => {
              const newGrid = prevState.animatedGrid;
              const newNode = {
                ...node,
                isVisited: true
              };
              newGrid[node.row][node.col] = newNode;
              return { animatedGrid: newGrid };
            });
          }, 30 * i); // 🟥 CHANGE BACK TO 1000 FOR 1 second
        });
      }
    
      visualizeDijkstra() {
        const { grid } = this.state;
        const startNode = grid[START_NODE_ROW][START_NODE_COL];
        const finishNode = grid[FINISH_NODE_ROW][FINISH_NODE_COL];
        const visitedNodesInOrder = dijkstra(grid, startNode, finishNode);
        this.animateDijkstra(visitedNodesInOrder);
      }
    
      render() {
        const { animatedGrid } = this.state; // 🟥 using the new state here
    
        return (
          <>
            <button onClick={() => this.visualizeDijkstra()}>
              Visualize Dijkstra Algorithm
            </button>
            <div className="grid">
              {animatedGrid.map((row, rowIdx) => {
                // 🟥 using the new state here
                return (
                  <div key={rowIdx}>
                    {row.map((node, nodeIdx) => {
                      const { isStart, isFinish, isVisited } = node;
                      return (
                        <Node
                          key={nodeIdx}
                          isStart={isStart}
                          isFinish={isFinish}
                          isVisited={isVisited}
                        ></Node>
                      );
                    })}
                  </div>
                );
              })}
            </div>
          </>
        );
      }
    }
    const getInitialGrid = () => {
      const grid = [];
    
      for (let row = 0; row < 20; row++) {
        const currentRow = [];
        for (let col = 0; col < 50; col++) {
          currentRow.push(createNode(col, row));
        }
    
        grid.push(currentRow);
      }
      return grid;
    };
    
    const createNode = (col, row) => {
      return {
        col,
        row,
        isStart: row === START_NODE_ROW && col === START_NODE_COL,
        isFinish: row === FINISH_NODE_ROW && col === FINISH_NODE_COL,
        distance: Infinity,
        isVisited: false,
        previousNode: null
      };
    };