This is part 2 of the Bishop Path Traversal series. Before starting reading this blog post make sure that you have gone through Bishop Path Traversal - Part 1 of the series.
In part 1, I have explained the problem statement and underlying logic of the solution in detail with diagrammatic representation.
Problem statement:
Create a react application to map the origin and destination point of Bishop and also the traversable path.
This will be the final UI that we would be producing in this tutorial.
Check out the demo app here
Step 1:
We need to create the react application. I will be using pnpm with vite.
To install pnpm:
npm install -g pnpm
pnpm is alternative solution for npm with many advantages.
Vite:
Vite is a build tool that aims to provide a faster and leaner development experience for modern web projects. Vite combined with pnpm provides much faster and smooth developer experience compared to other options such as create-react-app
.
Bootstrapping the project:
$: pnpm create vite
√ Project name: ... bishop-move-traverse
√ Select a framework: » react
√ Select a variant: » react-ts
$: cd bishop-move-traverse
$: pnpm i
$: pnpm run dev
Now we can do a little bit of clean up to remove all these default things that came with the project.
Delete the following:
- App.css
- logo.svg
- contents of index.css
- Delete contents of App.tsx
App.tsx
import React from 'react';
export const App: React.FC = () => {
return <div>App</div>;
};
To keep things organized, create a components and utility folders.
Let's analyze what are all the components in our applications.
- NumInput: It will include a label, defaultValue and an onChange method to update our state.
- PointInput: Since we have have 2 inputs for each point, we can easily extract that into a component.
- ColorCoders: To will show the meaning each color on the grid.
- BoardCell: To show the co-ordinates, with different background-color to represent the cells behaviour.
- BoardRow: To represent each row in a same line.
- CanTraverse: To show whether we can traverse from the given start to end point.
Most of the state of our app will be present at the root component which is App.tsx
Note: You can give your own styling with the same class names or can copy from the Github repo.
Create an interface to represent the points in the grid:
export interface IPoint {
x: number;
y: number;
}
Let's start by creating the state for our input fields and pass it to the PointInput component.
import React, { useState } from 'react';
import { PointInput } from './components/PointInput';
import { IPoint } from './utility/types';
export const App: React.FC = () => {
const [startPoint, setStartPoint] = useState<IPoint>({ x: 0, y: 0 });
const [endPoint, setEndPoint] = useState<IPoint>({ x: 2, y: 2 });
return (
<div className='Container'>
<div className='InputSection'>
<PointInput point={startPoint} setPoint={setStartPoint} type='Start' />
<PointInput point={endPoint} setPoint={setEndPoint} type='End' />
</div>
<div className='ChessboardSection'>Chessboard grid</div>
</div>
);
};
PointInput Component:
import { FC } from 'react';
import { IPoint, } from '../utility/types';
import { isValidCoordinate } from '../utility/utils';
import { NumInput } from './NumInput';
export interface IPointInputProps {
point: IPoint;
setPoint: (ipoint: IPoint) => void;
type: 'Start' | 'End';
}
export const PointInput: FC<IPointInputProps> = ({ point, setPoint, type }) => {
return (
<div className='InputGroup'>
<NumInput
defaultValue={point.x}
onChange={(e) => {
const n = parseInt(e.target.value);
if (isValidCoordinate(n)) setPoint({ x: n, y: point.y });
}}
label={`${type} Point Row`}
/>
<NumInput
defaultValue={point.y}
onChange={(e) => {
const n = parseInt(e.target.value);
if (isValidCoordinate(n)) setPoint({ x: point.x, y: n });
}}
label={`${type} Point Col`}
/>
</div>
);
};
And the NumInput component:
import { FC } from 'react';
import { CHESS_BOARD_SIZE } from '../utility/utils';
export interface INumInputProps {
defaultValue: number;
label: string;
onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
}
export const NumInput: FC<INumInputProps> = ({
defaultValue,
onChange,
label,
}) => {
return (
<div className='input-field'>
<label>{label}</label>
<input
min={0}
max={CHESS_BOARD_SIZE - 1}
type='number'
defaultValue={defaultValue}
onChange={(e) => onChange(e)}
/>
</div>
);
};