首页 > 解决方案 > 组件在 useState 挂钩更新时不重新渲染

问题描述

export default function SearchPage() {
    const [searchString, setSearchString] = React.useState("");
    const [apiCall, setApiCall] = React.useState<() => Promise<Collection>>();
    const {isIdle, isLoading, isError, error, data} = useApi(apiCall);
    const api = useContext(ApiContext);

    useEffect(()=>console.log("APICall changed to", apiCall), [apiCall]);

    const doSearch = (event: React.FormEvent) => {
        event.preventDefault();
        setApiCall(() => () => api.search(searchString));
    };

    const doNext = () => {
        var next = api.next;
        if (next) {
            setApiCall(()=>(() => next)());
        }
        window.scrollTo(0, 0);
    }

    const doPrev = () => {
        if (api.prev) {
            setApiCall(() => api.prev);
        }
        window.scrollTo(0, 0);
    }

    return (
        <>
            <form className={"searchBoxContainer"} onSubmit={doSearch}>
                <TextField
                    label={"Search"}
                    variant={"filled"}
                    value={searchString}
                    onChange={handleChange}
                    className={"searchBox"}
                    InputProps={{
                        endAdornment: (
                            <IconButton onClick={() => setSearchString("")}>
                                <ClearIcon/>
                            </IconButton>
                        )
                    }}
                />
                <Button type={"submit"} variant={"contained"} className={"searchButton"}>Go</Button>
            </form>

            {
                (isIdle) ? (
                    <span/>
                ) : isLoading ? (
                    <span>Loading...</span>
                ) : isError ? (
                    <span>Error: {error}</span>
                ) : (
                    <Paper className={"searchResultsContainer"}>
                        <Box className={"navButtonContainer"}>
                            <Button variant={"contained"}
                                    disabled={!api.prev}
                                    onClick={doPrev}
                                    className={"navButton"}>
                                {"< Prev"}
                            </Button>
                            <Button variant={"contained"}
                                    disabled={!api.next}
                                    onClick={doNext}
                                    className={"navButton"}>
                                {"Next >"}
                            </Button>
                        </Box>
                        <Box className={"searchResults"}>
                            {
                                data && data.items().all().map(item => (
                                    <span className={"thumbnailWrapper"}>
                                    <img className={"thumbnail"}
                                         src={item.link("preview")?.href}
                                         alt={(Array.from(item.allData())[0].object as SearchResponseDataModel).title}/>
                                </span>
                                ))
                            }
                        </Box>
                        <Box className={"navButtonContainer"}>
                            <Button variant={"contained"}
                                    disabled={!api.prev}
                                    onClick={doPrev}
                                    className={"navButton"}>
                                {"< Prev"}
                            </Button>
                            <Button variant={"contained"}
                                    disabled={!api.next}
                                    onClick={doNext}
                                    className={"navButton"}>
                                {"Next >"}
                            </Button>
                        </Box>
                    </Paper>
                )
            }
        </>
    )
}

由于各种原因,我在我的状态中存储了一个函数(它与react-query库一起使用)。不过,当我尝试更新它时,我看到了非常奇怪的行为。当任何doSearch,doNextdoPrev被调用时,它会成功更新状态 -useEffect钩子正在正确触发,我可以在控制台中看到消息 - 但它不会触发重新渲染,直到窗口失去并重新获得焦点。

我见过的大多数其他人都在以他们的状态存储一个数组,并更新数组而不是创建一个新的数组——所以钩子不会把它当作一个新对象,并且重新渲染不会发生。不过,我没有使用数组,我使用的是一个函数,并将不同的函数对象传递给它。我完全被难住了,不知道发生了什么。

编辑:似乎它可能不是渲染失败,但查询钩子没有注意到它的输入已经改变?我已经编辑了上面的代码以显示整个函数,下面是我的自定义钩子。

function useApi(func?: () => Promise<Collection>) {
    return useQuery(
        ["doApiCall", func],
        func || (async () => await undefined),
        {
            enabled: !!func,
            keepPreviousData: true
        }
    )
}

标签: reactjstypescriptreact-hooksreact-query

解决方案


您不能将函数放入 queryKey。密钥需要可序列化。请参阅:https ://react-query.tanstack.com/guides/query-keys#array-keys


推荐阅读