我在React中有一种奇怪的问题。我正在使用Blitz JS框架以及Prisma数据库的东西。
我有一个函数,查询数据库中从用户选择的日期开始的所有条目。它用于我正在尝试构建的预订系统。
在我获得数据后,我使用它来创建<select>
元素并将未出现在数据库中的每个空间设置为<option>
。一切工作正常,<select>
和<option>
显示他们应该,但只要我点击下拉菜单看到所有可用的选项,我认为状态刷新和菜单关闭。
如果我在函数内设置console.log()
,它将永远在控制台菜单中运行。在终端中,我可以看到这个函数大约每秒钟被调用一次。
终端的日志
javascript console log
我也尝试从useEffect()
查询数据库,但useEffect()
和useQuery
(从闪电战.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
hook:
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的生命周期。如果你不了解它,一定要深入研究一下。
https://www.w3schools.com/react/react_lifecycle.asp: ~:文本= % 20每个% 20组件% 20反应% 20,安装% 2 c % 20更新% 2 c % 20和% 20卸载。
当你渲染你的组件时,它调用PescuitSelect()函数
,在这个函数中
setAvailableSpots({ ...availableSpots, pescuit: availableFishingSpots })
您的一个状态将被更新。在React中,当状态更新时,组件将再次刷新以显示该状态的新数据
Issue
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组件是一种反模式。它们都应该在顶层声明。如果有必要,将任何回调函数作为props传入,而不是尝试使用外部作用域封闭的值/回调函数。
还有一个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
组件声明移到之外thisAdd
component.
由于PescuitSelect
似乎没有任何状态,计算可用钓鱼点的逻辑只存在于更新父节点中的状态,因此应该将该逻辑移动到父节点,并将availableSpots
数组作为prop传递给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>
</>
)
}
标题>