我在React中有这个Dashboard
组件:
export default function Dashboard() {
const navigate = useNavigate();
useEffect(() => {
async function checkSession() {
const { data, error } = await supabase.auth.getSession();
console.log(data);
console.log(error);
if (data.session === null) {
navigate("/signup");
}
}
checkSession();
}, [navigate]);
const [sidebarOpen, setSidebarOpen] = useState(false);
const [documents, setDocuments] = useState([]);
const [fetchingDocuments, setFetchingDocuments] = useState(true);
const [open, setOpen] = useState(false);
async function getDocuments() {
setFetchingDocuments(true);
const uid = await getUserId();
// if (uid !== null) {
// setUid(uid);
// }
console.log(uid);
const { data, error } = await supabase.storage
.from("pdf-documents")
.list(uid);
if (error !== null) throw error;
console.log({
"former state": documents,
"new state": data,
});
setDocuments([...data]);
setFetchingDocuments(false);
}
useEffect(() => {
getDocuments();
}, []);
return (
<div>
<MobileSideNavigation
sidebarOpen={sidebarOpen}
setSidebarOpen={setSidebarOpen}
/>
{/* Static sidebar for desktop */}
{/* <div className="flex flex-shrink-0 bg-gray-700 p-4">
<button className="group block w-full flex-shrink-0">
<div className="flex items-center">
<div>
<img
className="inline-block h-9 w-9 rounded-full"
src="https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80"
alt=""
/>
</div>
<div className="ml-3">
<p className="text-sm font-medium text-white">Tom Cook</p>
<p className="text-xs font-medium text-gray-300 group-hover:text-gray-200">View profile</p>
</div>
</div>
</button>
</div> */}
<DesktopSideNavigation />
<div className="flex flex-1 flex-col lg:pl-64">
<div className="sticky top-0 z-10 bg-gray-100 pl-1 pt-1 sm:pl-3 sm:pt-3 lg:hidden">
<button
type="button"
className="-ml-0.5 -mt-0.5 inline-flex h-12 w-12 items-center justify-center rounded-md text-gray-500 hover:text-gray-900 focus:outline-none focus:ring-2 focus:ring-inset focus:ring-indigo-500"
onClick={() => setSidebarOpen(true)}
>
<span className="sr-only">Open sidebar</span>
<Bars3Icon className="h-6 w-6" aria-hidden="true" />
</button>
</div>
<main className="flex-1">
<div className="py-6">
<div className="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
<h1 className="text-2xl font-semibold text-gray-900">
Documents
</h1>
</div>
<div className="py-2"></div>
<div className="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
<div className="flex-wrap flex items-center flex space-x-4">
<div className="text-center">
<svg
className="mx-auto h-12 w-12 text-gray-400"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
aria-hidden="true"
>
<path
vectorEffect="non-scaling-stroke"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M9 13h6m-3-3v6m-9 1V7a2 2 0 012-2h6l2 2h6a2 2 0 012 2v8a2 2 0 01-2 2H5a2 2 0 01-2-2z"
/>
</svg>
{fetchingDocuments === true ? (
<FetchingDocumentText />
) : (
<UploadDocumentText
documentLength={documents.length}
/>
)}
<div className="mt-3">
<button
type="button"
className="inline-flex items-center rounded-md bg-indigo-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600"
onClick={() => setOpen(true)}
>
<PlusIcon
className="-ml-0.5 mr-1.5 h-5 w-5"
aria-hidden="true"
/>
Upload Document
</button>
</div>
<FilePickerModal
open={open}
setOpen={setOpen}
// handleOnChange={handleDocumentsOnChange}
/>
{/* </form> */}
</div>
{documents.map((doc) => {
return (
<div
key={doc.id}
className="border-solid border rounded-md border-gray-500 cursor-pointer hover:border-blue-500"
>
<ImagePreview
pdf={doc}
containerHeight={125}
containerWidth={175}
getDocuments={getDocuments}
/>
</div>
);
})}
</div>
</div>
</div>
</main>
</div>
</div>
);
}
ImagePreview
组件具有调用Dashboard
组件中的getDocuments
函数的模态:
function ImagePreview({ pdf, containerWidth, containerHeight, getDocuments }) {
const canvasRef = useRef(null);
const [title, setTitle] = useState("");
const [date, setDate] = useState("");
const [ellipsisModalOpen, setEllipsisModalOpen] = useState(false);
const [deleteModalOpen, setDeleteModalOpen] = useState(false);
const [renameModalOpen, setRenameModalOpen] = useState(false);
useEffect(() => {
async function init() {
try {
const canvas = canvasRef.current;
const ctx = canvas.getContext("2d");
const uid = await getUserId();
const publicUrl = await supabase.storage.from("pdf-documents").getPublicUrl(uid + "/" + pdf.name).data.publicUrl;
// Load the PDF file
pdfjsLib
.getDocument(publicUrl)
.promise.then((pdf) => {
// Get the specified page
return pdf.getPage(1);
})
.then((page) => {
// Set the canvas dimensions to match the PDF page dimensions
const viewport = page.getViewport({ scale: 1 });
const canvasWidth = containerWidth;
const canvasHeight = containerHeight;
const scaleX = canvasWidth / viewport.width;
const scaleY = canvasHeight / viewport.height;
const scale = Math.max(scaleX, scaleY);
const newViewport = page.getViewport({ scale });
// Set the canvas dimensions to match the fixed size
canvas.width = canvasWidth;
canvas.height = canvasHeight;
// Render the PDF page as an HTML5 canvas
const renderContext = {
canvasContext: ctx,
viewport: newViewport,
};
page.render(renderContext);
});
} catch (error) {
console.log(error);
}
}
init();
}, []);
useEffect(() => {
const date = new Date(pdf.last_accessed_at);
const options = { year: "numeric", month: "long", day: "numeric" };
const dateString = date.toLocaleDateString("en-PH", options);
setDate(dateString);
if (pdf.name.length > 16) {
console.log(
{
"old title: ": title,
"new title: ": pdf.name.substring(0, 16) + " ...",
}
);
setTitle(pdf.name.substring(0, 16) + " ...");
} else {
console.log(
{
"old title: ": title,
"new title: ": pdf.name,
},
);
setTitle(pdf.name);
}
}, []);
const openEllipsisModal = () => {
setEllipsisModalOpen(true);
};
const closeEllipsisModal = () => {
setEllipsisModalOpen(false);
};
const openDeleteModal = () => {
setDeleteModalOpen(true);
};
const closeDeleteModal = () => {
setDeleteModalOpen(false);
};
const openRenameModal = () => {
setRenameModalOpen(true);
};
const closeRenameModal = () => {
setRenameModalOpen(false);
};
// TODO: Add OnClick event to open a new page with the pdf
// TODO: Add a modal to delete/rename the pdf on onClick of EllipsisVerticalIcon
return (
<div>
<canvas ref={canvasRef} className="rounded-t-md" />
<div>
<div className="flex flex-col p-1">
<p className="px-1 text-gray-900 text-sm font-semibold">
{title}
</p>
<div className="flex flex-row space-x-1.5">
<p className="py-0.5 px-1 text-gray-500 text-xs font-normal">
Opened {date}
</p>
<div className="relative">
<EllipsisVerticalIcon
className="h-5 w-4 text-gray-500 cursor-pointer hover:bg-gray-200 rounded-full"
aria-hidden="true"
onClick={openEllipsisModal}
/>
<EllipsisModal open={ellipsisModalOpen} setClose={closeEllipsisModal} openDeleteModal={openDeleteModal} openRenameModal={openRenameModal} />
<DeleteModal open={deleteModalOpen} setClose={closeDeleteModal} pdfName={pdf.name} getDocuments={getDocuments}/>
<RenameModal open={renameModalOpen} setClose={closeRenameModal} pdfName={pdf.name} getDocuments={getDocuments}/>
</div>
</div>
</div>
</div>
</div>
);
}
function DeleteModal({ open, setClose, pdfName, getDocuments }) {
async function handleDeleteOnClick(e) {
e.preventDefault();
try {
const uid = await getUserId();
const { data, error } = await supabase.storage.from("pdf-documents").remove([uid + "/" + pdfName]);
console.log(data, error);
} catch (error) {
console.log(error);
}
setClose();
await getDocuments();
}
return (
<Transition.Root show={open} as={Fragment}>
<Dialog as="div" className="relative z-10" onClose={setClose}>
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="ease-in duration-200"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div className="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity" />
</Transition.Child>
<div className="fixed inset-0 z-10 overflow-y-auto">
<div className="flex min-h-full items-end justify-center p-4 text-center sm:items-center sm:p-0">
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
enterTo="opacity-100 translate-y-0 sm:scale-100"
leave="ease-in duration-200"
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
>
<Dialog.Panel className="relative transform overflow-hidden rounded-lg bg-white px-4 pb-4 pt-5 text-left shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-lg sm:p-6">
<div className="absolute right-0 top-0 hidden pr-4 pt-4 sm:block">
<button
type="button"
className="rounded-md bg-white text-gray-400 hover:text-gray-500 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2"
onClick={setClose}
>
<span className="sr-only">Close</span>
<XMarkIcon
className="h-6 w-6"
aria-hidden="true"
/>
</button>
</div>
<div className="sm:flex sm:items-start">
<div className="mt-3 text-center sm:ml-4 sm:mt-0 sm:text-left">
<Dialog.Title
as="h3"
className="text-base font-semibold leading-6 text-gray-900"
>
Deactivate account
</Dialog.Title>
<div className="mt-2">
<p className="text-sm text-gray-500">
Are you sure you want to
delete this reading? This action cannot be
undone.
</p>
</div>
</div>
</div>
<div className="mt-5 sm:mt-4 sm:flex sm:flex-row-reverse">
<button
type="button"
className="inline-flex w-full justify-center rounded-md bg-red-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-red-500 sm:ml-3 sm:w-auto"
onClick={handleDeleteOnClick}
>
Delete
</button>
<button
type="button"
className="mt-3 inline-flex w-full justify-center rounded-md bg-white px-3 py-2 text-sm font-semibold text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-gray-50 sm:mt-0 sm:w-auto"
onClick={setClose}
>
Cancel
</button>
</div>
</Dialog.Panel>
</Transition.Child>
</div>
</div>
</Dialog>
</Transition.Root>
);
}
function RenameModal({ open, setClose, pdfName, getDocuments }) {
const [newPdfName, setNewPdfName] = useState("");
async function handleRenameOnClick(e) {
e.preventDefault();
try {
const uid = await getUserId();
const { data, error } = await supabase.storage
.from("pdf-documents").move(uid + "/" + pdfName, uid + "/" + newPdfName);
console.log(data, error);
} catch (error) {
console.log(error);
}
setClose();
await getDocuments();
}
const handlePdfNameOnChange = (e) => {
const newPdfName = e.target.value;
console.log(newPdfName);
setNewPdfName(newPdfName);
};
return (
<Transition.Root show={open} as={Fragment}>
<Dialog as="div" className="relative z-10" onClose={setClose}>
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="ease-in duration-200"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div className="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity" />
</Transition.Child>
<div className="fixed inset-0 z-10 overflow-y-auto">
<div className="flex min-h-full items-end justify-center p-4 text-center sm:items-center sm:p-0">
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
enterTo="opacity-100 translate-y-0 sm:scale-100"
leave="ease-in duration-200"
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
>
<Dialog.Panel className="relative transform overflow-hidden rounded-lg bg-white px-4 pb-4 pt-5 text-left shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-lg sm:p-6">
<div className="absolute right-0 top-0 hidden pr-4 pt-4 sm:block">
<button
type="button"
className="rounded-md bg-white text-gray-400 hover:text-gray-500 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2"
onClick={setClose}
>
<span className="sr-only">Close</span>
<XMarkIcon
className="h-6 w-6"
aria-hidden="true"
/>
</button>
</div>
<div className="sm:flex sm:items-start">
<div className="mt-3 text-center sm:ml-4 sm:mt-0 sm:text-left">
<Dialog.Title
as="h3"
className="text-base font-semibold leading-6 text-gray-900"
>
Rename file
</Dialog.Title>
<div className="mt-2">
<p className="text-sm text-gray-500">
Please enter a new name for this
file:
</p>
</div>
</div>
</div>
<div className="mt-5 pl-4">
<form onSubmit={handleRenameOnClick}>
<div className="mt-1">
<input
id="pdfName"
onChange={handlePdfNameOnChange}
name="pdfName"
type="text"
required
className="block w-full appearance-none rounded-md border border-gray-300 px-3 py-2 placeholder-gray-400 shadow-sm focus:border-indigo-500 focus:outline-none focus:ring-indigo-500 sm:text-sm"
/>
</div>
</form>
</div>
<div className="mt-5 sm:mt-4 sm:flex sm:flex-row-reverse">
<button
type="button"
className="inline-flex w-full justify-center rounded-md bg-blue-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-blue-500 sm:ml-3 sm:w-auto"
onClick={handleRenameOnClick}
>
Rename
</button>
<button
type="button"
className="mt-3 inline-flex w-full justify-center rounded-md bg-white px-3 py-2 text-sm font-semibold text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-gray-50 sm:mt-0 sm:w-auto"
onClick={setClose}
>
Cancel
</button>
</div>
</Dialog.Panel>
</Transition.Child>
</div>
</div>
</Dialog>
</Transition.Root>
);
}
当getDocuments
在DeleteModal
中被调用时,它会导致Dashboard
组件的ImagePreview
标记中重新呈现,因为documents
变量发生了变化。然而,当在RenameModal
中调用getDocuments
时,尽管documents
状态发生了变化,但它不会导致对ImagePreview
标签的重新渲染,更具体地说,标题没有改变。另外,查看React Components查看器,标题已经更改,但它没有显示在UI中。要在UI上显示它,需要手动刷新浏览器。我该如何解决这个问题?
解决方案是在ImagePreview
标签中只有一个useEffect
,并将pdf
prop作为其依赖项:
useEffect(() => {
async function init() {
try {
const canvas = canvasRef.current;
const ctx = canvas.getContext("2d");
const uid = await getUserId();
const publicUrl = await supabase.storage.from("pdf-documents").getPublicUrl(uid + "/" + pdf.name).data.publicUrl;
// Load the PDF file
pdfjsLib
.getDocument(publicUrl)
.promise.then((pdf) => {
// Get the specified page
return pdf.getPage(1);
})
.then((page) => {
// Set the canvas dimensions to match the PDF page dimensions
const viewport = page.getViewport({ scale: 1 });
const canvasWidth = containerWidth;
const canvasHeight = containerHeight;
const scaleX = canvasWidth / viewport.width;
const scaleY = canvasHeight / viewport.height;
const scale = Math.max(scaleX, scaleY);
const newViewport = page.getViewport({ scale });
// Set the canvas dimensions to match the fixed size
canvas.width = canvasWidth;
canvas.height = canvasHeight;
// Render the PDF page as an HTML5 canvas
const renderContext = {
canvasContext: ctx,
viewport: newViewport,
};
page.render(renderContext);
});
} catch (error) {
console.log(error);
}
}
init();
const date = new Date(pdf.last_accessed_at);
const options = { year: "numeric", month: "long", day: "numeric" };
const dateString = date.toLocaleDateString("en-PH", options);
setDate(dateString);
if (pdf.name.length > 16) {
console.log({
"old title: ": title,
"new title: ": pdf.name.substring(0, 16) + " ...",
});
setTitle(pdf.name.substring(0, 16) + " ...");
} else {
console.log({
"old title: ": title,
"new title: ": pdf.name,
});
setTitle(pdf.name);
}
}, [pdf]);