带日期范围选择器的 RTK 查询,避免不必要的渲染并遵守钩子规则
RTK Query with Date- Range Picker, avoid unnecessary renders and abide rule of hooks
我有一个时间卡组件,在使用中每个用户可能有大量数据,因此在提供日期范围之前获取数据没有意义。对于 RTK 查询,使用钩子进行 api 调用(e.x。{data: timecards, isLoading, isSuccess} = useGetTimecardsQuery(userId) )。使用 material ui 日期范围选择器(或任何日期范围选择器),值最初为 null 或 "",直到用户选择日期,然后设置值。为了延迟提取,一种常见的方法是将提取放入函数或挂钩中,以等待选择日期并按下按钮。然而,由于 RTK 查询使用了一个钩子,它打破了钩子的规则,因为它没有将它放在组件函数的顶层主体中。我已尝试将搜索调用作为自己的组件并设法避免出现任何错误,但将组件传递给 onClick 却无法真正进行调用。
一直在四处寻找,但没有找到任何关于此的讨论,可能是我遗漏了 React/RTK 的某些内容。任何帮助将不胜感激。
Timecard 组件:(所有注释掉的行都是为了让它工作而进行的不同尝试)
export default function Timecard() {
const { user } = useContext(UserContext)
//const [isSearching, setIsSearching] = useState(false)
//const isMounted = useIsMounted();
// const [state, doFetch] = useAsyncFn(async () => {
// const response = await handleSearch();
// return response
// }, [])
const userId = user._id;
//const { data: brackets, isLoading: isBracketsLoading, isSuccess: isBracketsSuccess } = useGetTimeBracketByUserIdQuery(userId);
const [value, setValue] = useState(["", ""]);
console.log({ value })
// const [bracketState, setBracketState] = useState()
// const [bracketLoad, setBracketLoad] = useState()
// const [bracketSuccess, setBracketSuccess] = useState()
const {
data: visits,
isLoading,
isSuccess} = useGetVisitsByUserIdQuery(userId);
// const handleSearch = (async () => {
// // if (isSearching) {
// let startDate = value[0];
// let endDate = value[1];
// const { data: brackets, isLoading: isBracketsLoading, isSuccess: isBracketsSuccess } = useGetBracketsOfUserByDateQuery(userId, startDate, endDate);
// setBracketState(brackets)
// setBracketLoad(isBracketsLoading)
// setBracketSuccess(isBracketsSuccess)
// .then(() => setIsSearching(false));
// // } else if (isMounted.current) {
// // setIsSearching(false)
// // }
// })
// if (!isSearching) {
// setValue(["", ""])
// } else if (isSearching) {
// handleSearch()
// }
// useEffect(() => {
// if (isSearching) {
// const effect = async () => {
// await handleSearch();
// if (!isMounted()) return;
// setIsSearching(false);
// };
// effect();
// }
// }, [isSearching, isMounted, handleSearch]);
let bracketInfo = <div><h3>Select dates above and click run to view time card info</h3></div>
// if (bracketLoad) {
// bracketInfo = <div><h3>Loading...</h3></div>
// } else if (bracketSuccess) {
// bracketInfo =
// <div>
// <ul>
// {bracketState &&
// bracketState?.map((bracket) => (
// <TimeBracket key={bracket._id} bracket={bracket} />
// ))
// }
// </ul>
// </div>
// }
const handleClick = () => {
<DateSearch value={[value]} />
}
return (
<div>
<h1>Timecard</h1> <AddTimeBracket />
<br></br><br></br><br></br>
<LocalizationProvider dateAdapter={AdapterDateFns}>
<fieldset width='300px'>
<legend>Select Date Range</legend>
<DateRangePicker
value={value}
onChange={(newValue) => {
setValue(newValue);
}}
renderInput={(startProps, endProps) => (
<React.Fragment>
<TextField {...startProps} />
<Box sx={{ mx: 2}}> to </Box>
<TextField {...endProps} />
</React.Fragment>
)}
/>
<Button onClick={() => handleClick()} color='primary' variant='contained'>
Run
</Button>
</fieldset>
</LocalizationProvider>
<br></br><br></br><br></br><br></br>
<>
{bracketInfo}
</>
</div>
)
}
并且在子组件中调用 RTK 查询以避免违反挂钩规则:
export default function DateSearch ([value]) {
const [bracketState, setBracketState] = useState()
const [bracketLoad, setBracketLoad] = useState()
const [bracketSuccess, setBracketSuccess] = useState()
let startDate = value[0];
let endDate = value[1];
const { data: brackets, isLoading: isBracketsLoading, isSuccess: isBracketsSuccess } = useGetBracketsOfUserByDateQuery(userId, startDate, endDate);
setBracketState(brackets)
setBracketLoad(isBracketsLoading)
setBracketSuccess(isBracketsSuccess)
let bracketInfo
if (bracketLoad) {
bracketInfo = <div><h3>Loading...</h3></div>
} else if (bracketSuccess) {
bracketInfo =
<div>
<ul>
{bracketState &&
bracketState?.map((bracket) => (
<TimeBracket key={bracket._id} bracket={bracket} />
))
}
</ul>
</div>
}
}
更新
所以朝着正确的方向迈出了 1 步,但仍然失败了。为了解决渲染循环,为值的初始状态添加一个三元运算符以设置一个状态(isSearching),并给它一个回退值 [Date.now()] 允许组件加载。为了遵守挂钩规则并仅在组件主体中调用挂钩,我将 RTK 查询调用从 onClick 函数中移出,并让 onClick 更改组件状态的状态以触发 if 条件。但是,现在,一旦触发 onClick,组件就会中断,因为与之前的渲染相比,新渲染中调用了一个额外的钩子。因此,如果有人知道解决此问题的方法(或如何更好地构建整个组件以使其正常工作...),我将不胜感激。
export default function Timecard() {
const { user } = useContext(UserContext)
const [isSearching, setIsSearching] = useState(false)
const userId = user._id;
const [value, setValue] = useState(isSearching ? [] : [new Date(), new Date()]);
console.log({ value })
const [bracketState, setBracketState] = useState()
const [bracketLoad, setBracketLoad] = useState()
const [bracketSuccess, setBracketSuccess] = useState()
function handleSearch() {
setIsSearching(true)
}
if (isSearching) {
let startDate = value[0];
let endDate = value[1];
const { data: brackets, isLoading: isBracketsLoading, isSuccess: isBracketsSuccess } = useGetBracketsOfUserByDateQuery(userId, startDate, endDate);
setBracketState(brackets)
setBracketLoad(isBracketsLoading)
setBracketSuccess(isBracketsSuccess)
setIsSearching(false)
}
let bracketInfo = <div><h3>Select dates above and click run to view time card info</h3></div>
if (bracketLoad) {
bracketInfo = <div><h3>Loading...</h3></div>
} else if (bracketSuccess) {
bracketInfo =
<div>
<ul>
{bracketState &&
bracketState?.map((bracket) => (
<TimeBracket key={bracket._id} bracket={bracket} />
))
}
</ul>
</div>
}
return (
<div>
<h1>Timecard</h1> <AddTimeBracket />
<br></br><br></br><br></br>
<LocalizationProvider dateAdapter={AdapterDateFns}>
<fieldset width='300px'>
<legend>Select Date Range</legend>
<DateRangePicker
value={value}
onChange={(newValue) => {
setValue(newValue);
}}
renderInput={(startProps, endProps) => (
<React.Fragment>
<TextField {...startProps} />
<Box sx={{ mx: 2}}> to </Box>
<TextField {...endProps} />
</React.Fragment>
)}
/>
<Button onClick={() => handleSearch()} color='primary' variant='contained'>
Run
</Button>
</fieldset>
</LocalizationProvider>
<br></br><br></br>
<>
{bracketInfo}
</>
</div>
)
}
第二次更新
好的,所以在 if 条件中设置钩子也打破了钩子的规则。所以稍作调整,RTK 调用现在没有违反钩子规则,但无法访问日期范围条件,因为它只有 运行 一次......我不知道该去哪里。
export default function Timecard() {
const { user } = useContext(UserContext)
const [isSearching, setIsSearching] = useState(false)
const userId = user._id;
const [value, setValue] = useState(isSearching ? [] : [new Date(), new Date()]);
console.log({ value })
const [startDate, setStartDate] = useState(value[0])
const [endDate, setEndDate] = useState(value[1])
const [bracketState, setBracketState] = useState()
const [bracketLoad, setBracketLoad] = useState()
const [bracketSuccess, setBracketSuccess] = useState()
//let startDate = value[0];
//let endDate = value[1];
const { data: brackets,
isLoading: isBracketsLoading,
isSuccess: isBracketsSuccess
} = useGetBracketsOfUserByDateQuery(userId, startDate, endDate);
function handleSearch() {
setIsSearching(true)
}
if (isSearching) {
setBracketState(brackets)
setBracketLoad(isBracketsLoading)
setBracketSuccess(isBracketsSuccess)
setIsSearching(false)
}
let bracketInfo = <div><h3>Select dates above and click run to view time card info</h3></div>
if (bracketLoad) {
bracketInfo = <div><h3>Loading...</h3></div>
} else if (bracketSuccess) {
bracketInfo =
<div>
<ul>
{bracketState &&
bracketState?.map((bracket) => (
<TimeBracket key={bracket._id} bracket={bracket} />
))
}
</ul>
</div>
}
return (
<div>
<h1>Timecard</h1> <AddTimeBracket />
<br></br><br></br><br></br>
<LocalizationProvider dateAdapter={AdapterDateFns}>
<fieldset width='300px'>
<legend>Select Date Range</legend>
<DateRangePicker
value={value}
onChange={(newValue) => {
setValue(newValue);
setStartDate(newValue[0])
setEndDate(newValue[1])
console.log(startDate, endDate)
}}
renderInput={(startProps, endProps) => (
<React.Fragment>
<TextField {...startProps} />
<Box sx={{ mx: 2}}> to </Box>
<TextField {...endProps} />
</React.Fragment>
)}
/>
<Button onClick={() => handleSearch()} color='primary' variant='contained'>
Run
</Button>
</fieldset>
</LocalizationProvider>
<br></br><br></br>
<>
{bracketInfo}
</>
</div>
)
}
我认为您将竭尽全力重新创建
useLazyQuery
(在您第一次调用函数之前不会 运行),
- 将
skipToken
作为参数传递给挂钩(这将使查询不 运行)
- 在钩子调用上设置
skip
选项(不会进行查询 运行)
你看过这些吗?
我有一个时间卡组件,在使用中每个用户可能有大量数据,因此在提供日期范围之前获取数据没有意义。对于 RTK 查询,使用钩子进行 api 调用(e.x。{data: timecards, isLoading, isSuccess} = useGetTimecardsQuery(userId) )。使用 material ui 日期范围选择器(或任何日期范围选择器),值最初为 null 或 "",直到用户选择日期,然后设置值。为了延迟提取,一种常见的方法是将提取放入函数或挂钩中,以等待选择日期并按下按钮。然而,由于 RTK 查询使用了一个钩子,它打破了钩子的规则,因为它没有将它放在组件函数的顶层主体中。我已尝试将搜索调用作为自己的组件并设法避免出现任何错误,但将组件传递给 onClick 却无法真正进行调用。
一直在四处寻找,但没有找到任何关于此的讨论,可能是我遗漏了 React/RTK 的某些内容。任何帮助将不胜感激。
Timecard 组件:(所有注释掉的行都是为了让它工作而进行的不同尝试)
export default function Timecard() {
const { user } = useContext(UserContext)
//const [isSearching, setIsSearching] = useState(false)
//const isMounted = useIsMounted();
// const [state, doFetch] = useAsyncFn(async () => {
// const response = await handleSearch();
// return response
// }, [])
const userId = user._id;
//const { data: brackets, isLoading: isBracketsLoading, isSuccess: isBracketsSuccess } = useGetTimeBracketByUserIdQuery(userId);
const [value, setValue] = useState(["", ""]);
console.log({ value })
// const [bracketState, setBracketState] = useState()
// const [bracketLoad, setBracketLoad] = useState()
// const [bracketSuccess, setBracketSuccess] = useState()
const {
data: visits,
isLoading,
isSuccess} = useGetVisitsByUserIdQuery(userId);
// const handleSearch = (async () => {
// // if (isSearching) {
// let startDate = value[0];
// let endDate = value[1];
// const { data: brackets, isLoading: isBracketsLoading, isSuccess: isBracketsSuccess } = useGetBracketsOfUserByDateQuery(userId, startDate, endDate);
// setBracketState(brackets)
// setBracketLoad(isBracketsLoading)
// setBracketSuccess(isBracketsSuccess)
// .then(() => setIsSearching(false));
// // } else if (isMounted.current) {
// // setIsSearching(false)
// // }
// })
// if (!isSearching) {
// setValue(["", ""])
// } else if (isSearching) {
// handleSearch()
// }
// useEffect(() => {
// if (isSearching) {
// const effect = async () => {
// await handleSearch();
// if (!isMounted()) return;
// setIsSearching(false);
// };
// effect();
// }
// }, [isSearching, isMounted, handleSearch]);
let bracketInfo = <div><h3>Select dates above and click run to view time card info</h3></div>
// if (bracketLoad) {
// bracketInfo = <div><h3>Loading...</h3></div>
// } else if (bracketSuccess) {
// bracketInfo =
// <div>
// <ul>
// {bracketState &&
// bracketState?.map((bracket) => (
// <TimeBracket key={bracket._id} bracket={bracket} />
// ))
// }
// </ul>
// </div>
// }
const handleClick = () => {
<DateSearch value={[value]} />
}
return (
<div>
<h1>Timecard</h1> <AddTimeBracket />
<br></br><br></br><br></br>
<LocalizationProvider dateAdapter={AdapterDateFns}>
<fieldset width='300px'>
<legend>Select Date Range</legend>
<DateRangePicker
value={value}
onChange={(newValue) => {
setValue(newValue);
}}
renderInput={(startProps, endProps) => (
<React.Fragment>
<TextField {...startProps} />
<Box sx={{ mx: 2}}> to </Box>
<TextField {...endProps} />
</React.Fragment>
)}
/>
<Button onClick={() => handleClick()} color='primary' variant='contained'>
Run
</Button>
</fieldset>
</LocalizationProvider>
<br></br><br></br><br></br><br></br>
<>
{bracketInfo}
</>
</div>
)
}
并且在子组件中调用 RTK 查询以避免违反挂钩规则:
export default function DateSearch ([value]) {
const [bracketState, setBracketState] = useState()
const [bracketLoad, setBracketLoad] = useState()
const [bracketSuccess, setBracketSuccess] = useState()
let startDate = value[0];
let endDate = value[1];
const { data: brackets, isLoading: isBracketsLoading, isSuccess: isBracketsSuccess } = useGetBracketsOfUserByDateQuery(userId, startDate, endDate);
setBracketState(brackets)
setBracketLoad(isBracketsLoading)
setBracketSuccess(isBracketsSuccess)
let bracketInfo
if (bracketLoad) {
bracketInfo = <div><h3>Loading...</h3></div>
} else if (bracketSuccess) {
bracketInfo =
<div>
<ul>
{bracketState &&
bracketState?.map((bracket) => (
<TimeBracket key={bracket._id} bracket={bracket} />
))
}
</ul>
</div>
}
}
更新
所以朝着正确的方向迈出了 1 步,但仍然失败了。为了解决渲染循环,为值的初始状态添加一个三元运算符以设置一个状态(isSearching),并给它一个回退值 [Date.now()] 允许组件加载。为了遵守挂钩规则并仅在组件主体中调用挂钩,我将 RTK 查询调用从 onClick 函数中移出,并让 onClick 更改组件状态的状态以触发 if 条件。但是,现在,一旦触发 onClick,组件就会中断,因为与之前的渲染相比,新渲染中调用了一个额外的钩子。因此,如果有人知道解决此问题的方法(或如何更好地构建整个组件以使其正常工作...),我将不胜感激。
export default function Timecard() {
const { user } = useContext(UserContext)
const [isSearching, setIsSearching] = useState(false)
const userId = user._id;
const [value, setValue] = useState(isSearching ? [] : [new Date(), new Date()]);
console.log({ value })
const [bracketState, setBracketState] = useState()
const [bracketLoad, setBracketLoad] = useState()
const [bracketSuccess, setBracketSuccess] = useState()
function handleSearch() {
setIsSearching(true)
}
if (isSearching) {
let startDate = value[0];
let endDate = value[1];
const { data: brackets, isLoading: isBracketsLoading, isSuccess: isBracketsSuccess } = useGetBracketsOfUserByDateQuery(userId, startDate, endDate);
setBracketState(brackets)
setBracketLoad(isBracketsLoading)
setBracketSuccess(isBracketsSuccess)
setIsSearching(false)
}
let bracketInfo = <div><h3>Select dates above and click run to view time card info</h3></div>
if (bracketLoad) {
bracketInfo = <div><h3>Loading...</h3></div>
} else if (bracketSuccess) {
bracketInfo =
<div>
<ul>
{bracketState &&
bracketState?.map((bracket) => (
<TimeBracket key={bracket._id} bracket={bracket} />
))
}
</ul>
</div>
}
return (
<div>
<h1>Timecard</h1> <AddTimeBracket />
<br></br><br></br><br></br>
<LocalizationProvider dateAdapter={AdapterDateFns}>
<fieldset width='300px'>
<legend>Select Date Range</legend>
<DateRangePicker
value={value}
onChange={(newValue) => {
setValue(newValue);
}}
renderInput={(startProps, endProps) => (
<React.Fragment>
<TextField {...startProps} />
<Box sx={{ mx: 2}}> to </Box>
<TextField {...endProps} />
</React.Fragment>
)}
/>
<Button onClick={() => handleSearch()} color='primary' variant='contained'>
Run
</Button>
</fieldset>
</LocalizationProvider>
<br></br><br></br>
<>
{bracketInfo}
</>
</div>
)
}
第二次更新 好的,所以在 if 条件中设置钩子也打破了钩子的规则。所以稍作调整,RTK 调用现在没有违反钩子规则,但无法访问日期范围条件,因为它只有 运行 一次......我不知道该去哪里。
export default function Timecard() {
const { user } = useContext(UserContext)
const [isSearching, setIsSearching] = useState(false)
const userId = user._id;
const [value, setValue] = useState(isSearching ? [] : [new Date(), new Date()]);
console.log({ value })
const [startDate, setStartDate] = useState(value[0])
const [endDate, setEndDate] = useState(value[1])
const [bracketState, setBracketState] = useState()
const [bracketLoad, setBracketLoad] = useState()
const [bracketSuccess, setBracketSuccess] = useState()
//let startDate = value[0];
//let endDate = value[1];
const { data: brackets,
isLoading: isBracketsLoading,
isSuccess: isBracketsSuccess
} = useGetBracketsOfUserByDateQuery(userId, startDate, endDate);
function handleSearch() {
setIsSearching(true)
}
if (isSearching) {
setBracketState(brackets)
setBracketLoad(isBracketsLoading)
setBracketSuccess(isBracketsSuccess)
setIsSearching(false)
}
let bracketInfo = <div><h3>Select dates above and click run to view time card info</h3></div>
if (bracketLoad) {
bracketInfo = <div><h3>Loading...</h3></div>
} else if (bracketSuccess) {
bracketInfo =
<div>
<ul>
{bracketState &&
bracketState?.map((bracket) => (
<TimeBracket key={bracket._id} bracket={bracket} />
))
}
</ul>
</div>
}
return (
<div>
<h1>Timecard</h1> <AddTimeBracket />
<br></br><br></br><br></br>
<LocalizationProvider dateAdapter={AdapterDateFns}>
<fieldset width='300px'>
<legend>Select Date Range</legend>
<DateRangePicker
value={value}
onChange={(newValue) => {
setValue(newValue);
setStartDate(newValue[0])
setEndDate(newValue[1])
console.log(startDate, endDate)
}}
renderInput={(startProps, endProps) => (
<React.Fragment>
<TextField {...startProps} />
<Box sx={{ mx: 2}}> to </Box>
<TextField {...endProps} />
</React.Fragment>
)}
/>
<Button onClick={() => handleSearch()} color='primary' variant='contained'>
Run
</Button>
</fieldset>
</LocalizationProvider>
<br></br><br></br>
<>
{bracketInfo}
</>
</div>
)
}
我认为您将竭尽全力重新创建
useLazyQuery
(在您第一次调用函数之前不会 运行),- 将
skipToken
作为参数传递给挂钩(这将使查询不 运行) - 在钩子调用上设置
skip
选项(不会进行查询 运行)
你看过这些吗?