[英]React function executes continuously and state keeps refreshing
我在 React 中有一個奇怪的問題。 我將 Blitz JS 框架與 Prisma 一起用於數據庫。
我有一個函數可以從用戶選擇的日期開始查詢數據庫中的所有條目。 它用於我正在嘗試構建的預訂系統。
獲得數據后,我使用它創建一個<select>
元素並將數據庫中未出現的每個空間設置為<option>
。 一切正常, <select>
和<option>
顯示它們應該顯示的內容,但是一旦我單擊下拉列表以查看所有可用選項,我認為狀態會刷新並且菜單會關閉。
如果我在函數內部使用console.log()
,它將在控制台菜單中永遠存在。 同樣在終端中,我可以看到該函數大約每秒被調用一次。
我還嘗試從useEffect()
查詢數據庫,但useEffect()
和useQuery
(來自 Blitz.js)不能一起工作
為了便於閱讀,我將附上代碼和注釋。
感謝您的時間!
主頁:
import { BlitzPage, invoke, useQuery } from "blitz"
import { useState, useEffect, Suspense } from "react"
import { UserInfo } from "app/pages"
import DatePicker from "react-datepicker"
import "react-datepicker/dist/react-datepicker.css"
import addDays from "date-fns/addDays"
import format from "date-fns/format"
import insertBooking from "app/bookings/mutations/insertBooking"
import getAllBookings from "app/bookings/queries/getAllBookings"
import { useCurrentBookings } from "app/bookings/hooks/useCurrentBookings"
import { useCurrentUser } from "app/core/hooks/useCurrentUser"
const Add: BlitzPage = () => {
//State for all options that will be added for the booking
const [state, setState] = useState({
intrare: 1,
locParcare: 0,
locPescuit: 0,
casuta: 0,
sezlong: 0,
sedintaFoto: false,
petrecerePrivata: false,
totalPrice: 20,
})
//Date state added separately
const [startDate, setStartDate] = useState(addDays(new Date(), 1))
const [availableSpots, setAvailableSpots] = useState({
pescuit: [0],
casute: {},
sezlonguri: {},
})
// The function that reads the DB, manipulates the data so I can have
// an array of open spots and then renders those values in a select
const PescuitSelect = () => {
const totalFishingSpots = Array.from(Array(114).keys())
const bookings = useCurrentBookings(startDate) //useCurrentBookings is a hook I created
const availableFishingSpots = totalFishingSpots.filter(
(o1) => !bookings.some((o2) => o1 === o2.loc_pescuit)
)
console.log(availableFishingSpots)
setAvailableSpots({ ...availableSpots, pescuit: availableFishingSpots })
return (
<select>
{availableSpots.pescuit.map((value) => {
return (
<option value={value} key={value}>
{value}
</option>
)
})}
</select>
)
}
// Date state handler
const handleDate = (date) => {
setStartDate(date)
}
// Update the price as soon as any of the options changed
useEffect(() => {
const totalPrice =
state.intrare * 20 +
state.locParcare * 5 +
(state.casuta ? 100 : 0) +
(state.locPescuit ? 50 : 0) +
(state.sedintaFoto ? 100 : 0) +
state.sezlong * 15
setState({ ...state, totalPrice: totalPrice })
}, [state])
type booking = {
starts_at: Date
ends_at: Date
intrare_complex: number
loc_parcare: number
loc_pescuit: number
casuta: number
sezlong: number
sedinta_foto: boolean
petrecere_privata: boolean
total_price: number
}
// Here I handle the submit. "petrecerePrivata" means a private party. If that is checked
// it does something, if not, something else
function handleSubmit(event) {
event.preventDefault()
if (state.petrecerePrivata === true) {
setState({
...state,
intrare: 0,
locParcare: 0,
locPescuit: 0,
casuta: 0,
sezlong: 0,
sedintaFoto: false,
totalPrice: 100,
})
} else {
const booking: booking = {
starts_at: startDate,
ends_at: addDays(startDate, 1),
intrare_complex: state.intrare,
loc_parcare: state.locParcare,
loc_pescuit: state.locPescuit,
casuta: state.casuta,
sezlong: state.sezlong,
sedinta_foto: state.sedintaFoto,
petrecere_privata: state.petrecerePrivata,
total_price: state.totalPrice,
}
invoke(insertBooking, booking) // Insert the new created booking into the database
}
}
// State handler for everything but the price, that updates in the useEffect
const handleChange = (evt) => {
const name = evt.target.name
const value = evt.target.type === "checkbox" ? evt.target.checked : evt.target.value
setState({
...state,
[name]: value,
})
}
return (
<>
<Suspense fallback="Loading...">
<UserInfo />
</Suspense>
{
// Here starts the actual page itself
}
<div className="mx-auto max-w-xs ">
<div className="my-10 p-4 max-w-sm bg-white rounded-lg border border-gray-200 shadow-md sm:p-6 lg:p-8 dark:bg-gray-800 dark:border-gray-700">
<form className="space-y-6" action="#" onSubmit={handleSubmit}>
<h5 className="text-xl font-medium text-gray-900 dark:text-white">
Fa o rezervare noua
</h5>
{state.petrecerePrivata ? (
<>
<div>
<label
htmlFor="date"
className="block mb-2 text-sm font-medium text-gray-900 dark:text-gray-300"
>
Alege Data
</label>
<div className="border-2 rounded">
<DatePicker
selected={startDate}
onChange={(date) => handleDate(date)}
dateFormat="dd/MM/yyyy"
includeDateIntervals={[{ start: new Date(), end: addDays(new Date(), 30) }]}
className="cursor-pointer p-2"
/>
</div>
</div>
<label
htmlFor="checked-toggle"
className="relative inline-flex items-center mb-4 cursor-pointer"
>
<input
type="checkbox"
name="petrecerePrivata"
id="checked-toggle"
className="sr-only peer"
checked={state.petrecerePrivata}
onChange={handleChange}
/>
<div className="w-11 h-6 bg-gray-200 rounded-full peer peer-focus:ring-4 peer-focus:ring-blue-300 dark:peer-focus:ring-blue-800 dark:bg-gray-700 peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-0.5 after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all dark:border-gray-600 peer-checked:bg-blue-600"></div>
<span className="ml-3 text-sm font-medium text-gray-900 dark:text-gray-300">
Petrecere Privata
</span>
</label>
</>
) : (
<>
<div>
<label
htmlFor="date"
className="block mb-2 text-sm font-medium text-gray-900 dark:text-gray-300"
>
Alege Data
</label>
<div className="border-2 rounded">
<DatePicker
selected={startDate}
onChange={(date) => setStartDate(date)}
dateFormat="dd/MM/yyyy"
includeDateIntervals={[{ start: new Date(), end: addDays(new Date(), 30) }]}
className="cursor-pointer p-2"
/>
</div>
</div>
<div>
<label
htmlFor="intrare"
className="block mb-2 text-sm font-medium text-gray-900 dark:text-gray-300"
>
Bilete Intrare Complex
</label>
<input
type="number"
name="intrare"
id="intrare"
placeholder="1"
value={state.intrare}
onChange={handleChange}
className="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-600 dark:border-gray-500 dark:placeholder-gray-400 dark:text-white"
required
/>
</div>
<div>
<label
htmlFor="loParcare"
className="block mb-2 text-sm font-medium text-gray-900 dark:text-gray-300"
>
Numar Locuri de Parcare
</label>
<input
type="number"
name="locParcare"
id="locParcare"
placeholder="0"
min="0"
value={state.locParcare}
onChange={handleChange}
className="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-600 dark:border-gray-500 dark:placeholder-gray-400 dark:text-white"
/>
</div>
<div>
<label
htmlFor="locPescuit"
className="block mb-2 text-sm font-medium text-gray-900 dark:text-gray-300"
>
Alege Locul de Pescuit
</label>
{
// Here I call that function inside a Suspense and things go south
}
<Suspense fallback="Cautam locurile de pescuit">
<PescuitSelect />
</Suspense>
</div>
<div>
<label
htmlFor="casuta"
className="block mb-2 text-sm font-medium text-gray-900 dark:text-gray-300"
>
Alege Casuta
</label>
<input
type="number"
name="casuta"
id="casuta"
placeholder="0"
min="0"
max="18"
value={state.casuta}
onChange={handleChange}
className="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-600 dark:border-gray-500 dark:placeholder-gray-400 dark:text-white"
/>
</div>
<div>
<label
htmlFor="sezlong"
className="block mb-2 text-sm font-medium text-gray-900 dark:text-gray-300"
>
Alege Sezlong
</label>
<input
type="number"
name="sezlong"
id="sezlong"
placeholder="0"
min="0"
max="21"
value={state.sezlong}
onChange={handleChange}
className="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-600 dark:border-gray-500 dark:placeholder-gray-400 dark:text-white"
/>
</div>
<label
htmlFor="sedintaFoto"
className="relative inline-flex items-center mb-4 cursor-pointer"
>
<input
type="checkbox"
name="sedintaFoto"
id="sedintaFoto"
className="sr-only peer"
checked={state.sedintaFoto}
onChange={handleChange}
/>
<div className="w-11 h-6 bg-gray-200 rounded-full peer peer-focus:ring-4 peer-focus:ring-blue-300 dark:peer-focus:ring-blue-800 dark:bg-gray-700 peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-0.5 after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all dark:border-gray-600 peer-checked:bg-blue-600"></div>
<span className="ml-3 text-sm font-medium text-gray-900 dark:text-gray-300">
Sedinta foto
</span>
</label>
<label
htmlFor="petrecerePrivata"
className="relative inline-flex items-center mb-4 cursor-pointer"
>
<input
type="checkbox"
name="petrecerePrivata"
id="petrecerePrivata"
className="sr-only peer"
checked={state.petrecerePrivata}
onChange={handleChange}
/>
<div className="w-11 h-6 bg-gray-200 rounded-full peer peer-focus:ring-4 peer-focus:ring-blue-300 dark:peer-focus:ring-blue-800 dark:bg-gray-700 peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-0.5 after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all dark:border-gray-600 peer-checked:bg-blue-600"></div>
<span className="ml-3 text-sm font-medium text-gray-900 dark:text-gray-300">
Petrecere Privata
</span>
</label>
</>
)}
<button
type="submit"
className="w-full text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800"
>
Subimt
</button>
</form>
</div>
</div>
</>
)
}
export default Add
useCurrentBookings
鈎子:
import { useQuery } from "blitz"
import getAllBookings from "../queries/getAllBookings"
import format from "date-fns/format"
export const useCurrentBookings = (startDate) => {
const [booking] = useQuery(getAllBookings, format(startDate, "yyyy-MM-dd")) // Here I query the database
return booking
}
對數據庫的實際調用:
import db from "db"
//And this is the actual call to the database
export default async function getAllBookings(startsAt: string) {
return await db.booking.findMany({
where: { starts_at: { gte: new Date(startsAt) } },
})
}
useEffect() 每次依賴項更改時都會運行,在 useEffect 中您更新了狀態並再次調用 useEffect。 導致無限循環。
解析度:
const [totalPrice, setTotalPrice] = useState(0);
useEffect(() => {
const totalPrice =
state.intrare * 20 +
state.locParcare * 5 +
(state.casuta ? 100 : 0) +
(state.locPescuit ? 50 : 0) +
(state.sedintaFoto ? 100 : 0) +
state.sezlong * 15
setTotalPrice(totalPrice);
}, [state])
我之前遇到過這個問題,一直刷新一個 react 組件是因為 React 中的 Lifecycle。 如果您對此一無所知,請務必深入研究。
當你渲染你的組件時,它會調用 PescuitSelect() 函數
在這個函數中
setAvailableSpots({ ...availableSpots, pescuit: availableFishingSpots })
您的狀態之一將被更新。 在 React 中,當狀態更新時,組件將再次刷新以顯示該狀態的新數據
PescuitSelect
組件無條件更新狀態,這是一個無意的副作用並觸發重新渲染。
const PescuitSelect = () => {
const totalFishingSpots = Array.from(Array(114).keys())
const bookings = useCurrentBookings(startDate) //useCurrentBookings is a hook I created
const availableFishingSpots = totalFishingSpots.filter(
(o1) => !bookings.some((o2) => o1 === o2.loc_pescuit)
)
console.log(availableFishingSpots)
setAvailableSpots({ // <-- unconditional state update
...availableSpots,
pescuit: availableFishingSpots
})
return (
<select>
{availableSpots.pescuit.map((value) => {
return (
<option value={value} key={value}>
{value}
</option>
)
})}
</select>
)
}
最重要的是, PescuitSelect
在每個渲染周期都會重新聲明,因為它是在另一個 React 組件中定義的。 在 React 組件中聲明 React 組件是一種反模式。 它們都應該在頂層聲明。 如有必要,將任何回調作為道具傳遞,而不是嘗試使用從外部范圍關閉的值/回調。
還有一個useEffect
鈎子正在更新它作為其依賴項使用的狀態。
// Update the price as soon as any of the options changed
useEffect(() => {
const totalPrice =
state.intrare * 20 +
state.locParcare * 5 +
(state.casuta ? 100 : 0) +
(state.locPescuit ? 50 : 0) +
(state.sedintaFoto ? 100 : 0) +
state.sezlong * 15
setState({ ...state, totalPrice: totalPrice })
}, [state]);
更新state.totalPrice
更新state
值並觸發重新渲染,這將導致效果再次運行並將另一個狀態更新入隊。 此totalPrice
狀態很容易從其他現有狀態派生,因此不必也存儲在狀態中。
將PescuitSelect
組件聲明移到此Add
組件之外。
由於PescuitSelect
似乎沒有任何狀態,並且計算可用釣魚點的邏輯只存在於更新父級中的狀態,因此應該將此邏輯移至父級,並將availableSpots
數組作為道具傳遞給PescuitSelect
。
例子:
const PescuitSelect = ({ options }) => (
<select>
{options.map((value) => (
<option value={value} key={value}>
{value}
</option>
))}
</select>
);
被移動的邏輯應該放在一個useEffect
掛鈎中。 添加任何必要的依賴項。
刪除僅計算totalPrice
的useEffect
掛鈎,並在每次渲染時計算此值。 如果像這樣的計算很昂貴,那么使用useMemo
掛鈎來記憶結果。
例子:
type booking = {
starts_at: Date
ends_at: Date
intrare_complex: number
loc_parcare: number
loc_pescuit: number
casuta: number
sezlong: number
sedinta_foto: boolean
petrecere_privata: boolean
total_price: number
}
const Add: BlitzPage = () => {
//State for all options that will be added for the booking
const [state, setState] = useState({
intrare: 1,
locParcare: 0,
locPescuit: 0,
casuta: 0,
sezlong: 0,
sedintaFoto: false,
petrecerePrivata: false,
});
...
const [availableSpots, setAvailableSpots] = useState({
pescuit: [0],
casute: {},
sezlonguri: {},
});
const bookings = useCurrentBookings(startDate);
useEffect(() => {
const availableFishingSpots = Array.from(Array(114).keys())
.filter(o1 => !bookings.some((o2) => o1 === o2.loc_pescuit));
console.log(availableFishingSpots);
setAvailableSpots(availableSpots => ({
...availableSpots,
pescuit: availableFishingSpots,
}));
}, [bookings]);
...
// Update the price as soon as any of the options changed
const totalPrice = useMemo(() => {
return state.intrare * 20 +
state.locParcare * 5 +
(state.casuta ? 100 : 0) +
(state.locPescuit ? 50 : 0) +
(state.sedintaFoto ? 100 : 0) +
state.sezlong * 15;
}, [state]);
...
return (
<>
...
<div className="mx-auto max-w-xs ">
<div className="....">
<form className="space-y-6" action="#" onSubmit={handleSubmit}>
...
{state.petrecerePrivata ? (
...
) : (
<>
...
<div>
...
<Suspense fallback="Cautam locurile de pescuit">
<PescuitSelect options={availableSpots.pescuit} />
</Suspense>
</div>
...
</>
)}
...
</form>
</div>
</div>
</>
)
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.