等待钩子完成更新组件,然后再将值传递给表单



我最近一直有以下问题。我想创建一个简单的面板添加新的文章或新闻到我的应用程序使用Firebase。我创建了一个自定义挂钩获得最高的当前文章ID,它似乎工作正常。不幸的是,当我在管理面板组件中使用它时,第一个值(未定义或NaN)正在传递给表单。我希望这个值被显示为表单的一部分,是一个禁用的输入。作为文本输入,我创建了一个自定义和风格化的组件,我只在其中传递我需要的值。我怎么能让它等待钩子更新(即从DB获得实际值),然后将其传递给输入组件?值得一提的是:Typescript阻止我使用

if (highestID === NaN) { return }

表示类型number | undefined在类型number中不存在。

这是我的useGetHighestId。ts钩
import { db } from "../config/firebaseConfig";
import { collection, query, doc, getDoc, getDocs, DocumentSnapshot } from "firebase/firestore";
import { useEffect, useState } from "react";
import { ArticleParameters } from "../AdminPanel/AdminPanel";
export const useHighestId = (category?: string): number => {
const [allCategories, setAllCategories] = useState<any[]>([]);
const [articlesList, setArticlesList] = useState<any>({});
const [highestId, setHighestId] = useState<number>(0);
useEffect(() => {
const getCategory = async () => {
const collectDataFromDB = query(collection(db, "content"));
const dataFromDB: any[] = [];
const documents = await getDocs(collectDataFromDB);
documents.docs.map((document) => {
dataFromDB.push(document.id);
});
setAllCategories(dataFromDB);
};
getCategory();
}, []);
useEffect(() => {
allCategories.map((currentCategory) => {
const getArticlesFromDB = async () => {
const docRef = doc(db, "content", currentCategory);
const docSnap = await getDoc(docRef);
setArticlesList(docSnap.data());
};
getArticlesFromDB();
});
}, [allCategories]);
const articlesListAsArray: ArticleParameters[] = Object.values(articlesList);
const articlesIds: number[] = [];
articlesListAsArray.forEach((article) => {
articlesIds.push(article?.id);
});
useEffect(() => {
if (articlesIds.sort().reverse()[0] !== undefined) {
setHighestId(articlesIds.sort().reverse()[0]);
}
}, [articlesIds]);
return highestId;
};

下面是管理面板的实现:

import { useUser } from "../hooks/useUser";
import { db, storage } from "../config/firebaseConfig";
import { doc, setDoc, collection, getDocs, addDoc, query, Timestamp, updateDoc } from "firebase/firestore";
import { ChangeEvent, FormEvent, FormEventHandler, useEffect, useState } from "react";
import { Button, Col, Form, FormCheck, Row } from "react-bootstrap";
import { TextInput } from "../utils/TextInput";
import { useHighestId } from "../hooks/useHighestId";
import { getDownloadURL, ref, uploadBytesResumable } from "firebase/storage";
export interface ArticleParameters {
category: string;
id: number;
title: string;
content: string;
author: string;
date: Timestamp;
tags: string[];
isOnline: boolean;
isAdult: boolean;
databaseTitle: string;
}
const currentDate = new Date();
export const AdminPanel = () => {
const user = useUser();
let highestIdFromHook = useHighestId();
const parameters: ArticleParameters = {
category: "news",
id: highestIdFromHook + 1,
title: "",
content: "",
author: "",
date: Timestamp.fromDate(currentDate),
tags: [""],
isOnline: false,
isAdult: false,
databaseTitle: "",
};
const [data, setData] = useState<ArticleParameters>(parameters);
const [articleDatabaseName, setArticleDatabaseName] = useState<string>(`${data.category}_id_${data.id}`);
const [file, setFile] = useState<any>("");
const [percent, setPercent] = useState<number>(0);
// Handle file upload event and update state
function handleChange(event: any) {
setFile(event.target.files[0]);
}
const handleUpload = () => {
const storageRef = ref(storage, `/files/${file.name}`);
// progress can be paused and resumed. It also exposes progress updates.
// Receives the storage reference and the file to upload.
const uploadTask = uploadBytesResumable(storageRef, file);
uploadTask.on(
"state_changed",
(snapshot) => {
const percent = Math.round((snapshot.bytesTransferred / snapshot.totalBytes) * 100);
// update progress
setPercent(percent);
},
(err) => console.log(err),
() => {
// download url
getDownloadURL(uploadTask.snapshot.ref).then((url) => {
console.log(url);
});
}
);
};
const addArticle = async (newArticleData: ArticleParameters) => {
await updateDoc(doc(db, "content", newArticleData.category), {
[newArticleData.databaseTitle]: {
id: newArticleData.id,
title: newArticleData.title,
content: newArticleData.content,
author: newArticleData.author,
date: newArticleData.date,
tags: newArticleData.tags,
is_online: newArticleData.isOnline,
is_adult: newArticleData.isAdult,
},
});
};
const handleSwitch = (e: ChangeEvent<HTMLInputElement>) => {
setData({ ...data, [e.target.name]: e.target.checked });
};
//   const handleChange = (e: any) => {
//     setFile(e.target.files[0]);
//   };
const { category, id, title, content, author, date, tags, isOnline, isAdult } = data;
const resetForm = () => {
setArticleDatabaseName(`${data.category}_id_${data.id}`);
setData({
category: "news",
id: highestIdFromHook + 1,
title: "",
content: "",
author: "",
date: Timestamp.fromDate(currentDate),
tags: [""],
isOnline: false,
isAdult: false,
databaseTitle: articleDatabaseName,
});
};
const handleSubmit = (e: FormEvent<HTMLFormElement>) => {
e.preventDefault();
if (!file) {
alert("Please upload an image first!");
}
if (data.databaseTitle !== "") {
addArticle(data);
handleUpload();
resetForm();
} else {
console.log("Shit");
}
};
return (
<div className="articles__content--wrapper">
<h1 className="text-center mb-4">Dodaj newsa</h1>
<Form
style={{
width: "80%",
alignSelf: "center",
alignItems: "center",
}}
onSubmit={handleSubmit}>
<TextInput input="Tytuł" isRequired="true" type="text" name="title" data={data} setData={setData} />
<TextInput input="Treść" isRequired="true" type="text" name="content" data={data} setData={setData} textarea />
<Form.Group controlId="formFile" className="mb-3">
<Form.Label>Dodaj obrazek</Form.Label>
<Form.Control className="form__control--input" type="file" accept="image/*" onChange={handleChange} />
</Form.Group>
<div style={{ display: "flex", flexDirection: "row", justifyContent: "space-between", alignItems: "center" }}>
<TextInput input="Data dodania" isRequired="true" disabled type="text" name="date" data={data} setData={setData} />
<TextInput input="ID Newsa" isRequired="true" disabled type="number" name="id" data={data} setData={setData} />
<div style={{ display: "flex", flexDirection: "column", justifyContent: "space-between", alignItems: "flex-start" }}>
<Form.Check type="switch" label="Opublikować?" id="isOnline" name="isOnline" onChange={handleSwitch} className="me-3"></Form.Check>
<Form.Check type="switch" label="Tylko dla dorosłych?" id="isAdult" name="isAdult" onChange={handleSwitch} className="mb-3 me-3"></Form.Check>
</div>
</div>
<TextInput input="Autor" isRequired="true" disabled type="text" name="author" data={data} setData={setData} />
<TextInput input="Nazwa w bazie" isRequired="true" disabled type="text" name="databaseTitle" data={data} setData={setData} />
<div style={{ display: "flex", flexDirection: "row", justifyContent: "space-around", alignItems: "center" }}>
<Button variant="info" type="submit">
Zapisz dane
</Button>
<Button variant="warning" type="reset" onClick={resetForm}>
Wyczyść formularz
</Button>
</div>
</Form>
</div>
);
};

和TextInput本身:

import FloatingLabel from "react-bootstrap/FloatingLabel";
import Form from "react-bootstrap/Form";
import { Dispatch, SetStateAction } from "react";
import { ArticleParameters } from "../AdminPanel/AdminPanel";
import { useUser } from "../hooks/useUser";
export interface DispatchTypes {
nickname?: string;
email: string;
password: string;
error?: string;
}
interface Properties {
input: string;
isRequired: string;
type: string;
name: string;
data: any;
setData: Dispatch<SetStateAction<DispatchTypes>> | Dispatch<SetStateAction<ArticleParameters>> | any;
textarea?: boolean;
disabled?: boolean;
}
export const TextInput = ({ input, isRequired, type, name, data, setData, textarea, disabled }: Properties) => {
const user = useUser();

const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setData({ ...data, [name]: e.target.value });
};
if (name === "author" && user?.name !== undefined) {
data.author = user?.name;
}
if (name === "databaseTitle") {
data.databaseTitle = `${data.category}_id_${data.id}`
}
return (
<FloatingLabel  label={`${input}${isRequired === "true" ? "*" : " (opcjonalnie)"}`} className="mb-3" style={{fontSize: "0.75rem"}}>
<Form.Control
as={textarea ? "textarea" : 'input'}
style={textarea ? {height: "200px", fontSize: "1rem"} : { height: "45px", fontSize: "1rem"}}
className="form__control--input"
type={type}
placeholder={`${name}${isRequired === "true" ? "*" : " (opcjonalnie)"}`}
required={isRequired === "true" ? true : false}
disabled={disabled ? true : false}
name={name}
onChange={handleChange}
value={name === "date" ? data.date.toDate() : data[name]}
/>
</FloatingLabel>
);
};

由于highestId在钩子抓取类别中默认为0,为什么不在AdminPanel中添加检查if(highestID <= 0) return null-这样你将在钩子返回值后开始渲染

最新更新