【问题标题】:Component not re-rendering when useState hook updates组件在 useState 挂钩更新时不重新渲染
【发布时间】:2021-04-13 11:13:08
【问题描述】:
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
        }
    )
}

【问题讨论】:

  • 如果钩子触发,那么它应该重新渲染。也许stuff 没有因为其他原因重新渲染?您可以尝试在组件顶部的 debug/console.log 中检查该函数是否按预期执行?
  • 人力资源部。该日志语句在钩子日志语句之前触发,所以它可能正在重新渲染,而查询钩子没有注意到更新?一会儿,我会添加更多代码
  • 我没有看到任何 react-query 参与发布的代码。另外,我怀疑您是否需要将函数存储在状态以进行反应查询。在 useEffect 中进行 api 调用也不是惯用的 react-query。您能否描述您的用例或显示反应查询代码
  • useEffect 调用纯粹是为了调试目的
  • 我的用例是我正在尝试与 API 进行基于反应查询的交互,并处理分页链接在 API 响应中返回而不是作为查询参数传递的事实使用查询,同时试图让组件完全不了解与 api 的所有实际交互,因为我希望能够完全封装所有直接与服务器对话的代码,并让组件纯粹通过哑接口与它通信

标签: reactjs typescript react-hooks react-query


【解决方案1】:

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

【讨论】:

  • 接受这个答案,因为这几乎肯定是我的查询没有更新的原因!
猜你喜欢
  • 2020-03-12
  • 1970-01-01
  • 2022-01-17
  • 2021-03-10
  • 1970-01-01
  • 2021-03-05
  • 1970-01-01
  • 2022-01-24
  • 2020-10-29
相关资源
最近更新 更多