useState 钩子更新时组件不会重新渲染

时间:2021-01-07 17:32:48

标签: reactjs typescript react-hooks react-query

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 库)。但是,当我尝试更新它时,我看到了非常奇怪的行为。当调用 doSearchdoNextdoPrev 中的任何一个时,它成功更新状态 - useEffect 钩子正确触发,我可以在控制台中看到消息 - 但是它不会触发重新渲染,直到窗口失去并重新获得焦点。

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

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

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

1 个答案:

答案 0 :(得分:1)

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