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.
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)
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
};
};