带日期范围选择器的 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 选项(不会进行查询 运行)

你看过这些吗?