I’m trying to create web app like spreadsheet.
instead of using table, I use my own components
I want to realize that when you click a cell, the cell turn gray.
I use Nextjs14, app router, typescript.
here is the error message
Error: Hydration failed because the initial UI does not match what was rendered on the server.
See more info here: https://nextjs.org/docs/messages/react-hydration-error
//page.tsx
export default function Home() {
//in the future, I will use server action and get data as 'lists'
//but now I just use this 'lists'
const lists = [
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19],
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19],
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19],
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19],
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19],
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19],
];
return (
<main className="p-3 w-full h-full ">
<div className=" h-full">
{/* table */}
<Table params={{ lists: lists }} />
</div>
</main>
);
}
//Table.tsx
"use client";
import React, { Suspense } from "react";
import RowHeader from "./RowHeader";
import RowData from "./RowData";
import { useClickStates } from "../hooks";
export default function Table({ params }: { params: { lists: number[][] } }) {
const undefinedList: number[] = [];
params.lists.map((i) => {
undefinedList.push(0);
});
const [clickStates, onClickHandler] = useClickStates({
params: { size: [params.lists.length, params.lists[0].length] },
});
return (
<div className="overflow-scroll w-full h-full text-basecolor-table ">
<RowHeader params={{ lists: params.lists.shift() ?? undefinedList }} />
<div className="w-full mt-2">
{params.lists.map((rowList, i) => {
return (
<RowData params={{ rowIndex: i,
lists: rowList,
useClickStates: [clickStates, onClickHandler],}}/>
); })}
</div>
</div>
);
}
//RowData.tsx
"use client";
import React, { useState } from "react";
import Cell from "./Cell";
import { useClickStates } from "../hooks";
export default function RowData({params,}: { params: { rowIndex: number; lists: number[]; useClickStates: [boolean[][] | null, (row: number, col: number) => void];}; }) {
const width = "w-32";
const [clickStates, onClickHandler] = params.useClickStates;
return (
<div className={`h-fit w-fit text-sm border-dark-gray-custom ${
params.rowIndex === 0 ? "border" : "border-x border-b"} flex `} >
{params.lists.map((l, i) => {
return i !== 0 ? (
/*No vertical dividing line in the first cell on the left,
Vertical dividing line only on the left thereafter.*/
<div key={`r${params.rowIndex}c${i}`}
className={`${width} h-fit flex items-center border-dark-gray-custom border-l ${
clickStates !== null
? clickStates[(params.rowIndex, i)]
? "bg-slate-300"
: ""
: "" }`}
onClick={() => onClickHandler(params.rowIndex, i)}>
<div className={`h-fit w-full px-1 `}>
<Cell params={{ value: l }} />
</div>
</div>
) : (
<div key={`r${params.rowIndex}c${i}`}
className={`${width} h-fit flex items-center ${
clickStates !== null
? clickStates[(params.rowIndex, i)]
? "bg-slate-300"
: ""
: "" }`}
onClick={() => onClickHandler(params.rowIndex, i)} >
<div className={`h-fit w-full px-1 `}>
<Cell params={{ value: l }} />
</div>
</div>
);})}
</div>
);
}
}
//hooks.ts
"use client";
import { useEffect, useState } from "react";
export const useClickStates = ({ params,}: { params: { size: number[] }; }
): [boolean[][] | null, (row: number, col: number) => void] => {
const clickStates_ini: boolean[][] = [];
//params.size[0] = 6;
//params.size[1] = 20;
for (let row = 0; row < params.size[0]; row++) {
const rowStates: boolean[] = [];
for (let col = 0; col < params.size[1]; col++) {
rowStates.push(false);
}
clickStates_ini.push(rowStates);
}
const [clickStates, setClickStates] = useState<boolean[][] | null>(null);
function onClickHandler(row: number, col: number) {
const newClickStates = [...clickStates_ini];
newClickStates[row][col] = true;
setClickStates(newClickStates);
}
useEffect(() => {
setClickStates(clickStates_ini);
}, []);
return [clickStates, onClickHandler];
};
//Cell.tsx
export default function Cell({ params }: { params: { value: number } }) {
const height = "h-7";
return <div className="w-full h-12 flex items-center">{params.value}</div>;
}
RowHeader.tsx is just shown correctly
in hooks.ts
I rewrite
const [clickStates, setClickStates] = useState<boolean[][]>(clickState_ini);
to
const [clickStates, setClickStates] = useState<boolean[][] | null>(null);
and use useEffect
I thought lists was probrem, so I put params.size[0] = 6; params.size[1] = 20;
for clickState_ini
also I did <Suspense fallback={<Loading/>}> <Table> </Suspense>
but still error