英文:
custom hook doesn't update when parent state changes
问题
以下是您提供的代码的翻译部分:
我是新手,可能说错了术语
我尝试从下面的代码中编写一个无限滚动
[github](https://github.com/gitdagray/react_infinite_scroll)
通过运行应用程序,从API获取了一些基本项,当我向下滚动时,*start*会改变,但不会发送新的数据请求。
我的自定义钩子
```javascript
import { useEffect, useState } from "react";
import { getData } from "../apis/coinmarketcapApi";
export default function useData({ start = 0 }) {
const [result, setResult] = useState([]);
const [error, setError] = useState({});
const [isError, setIsError] = useState(false);
const [isLoading, setIsLoading] = useState(false);
const [hasNextPage, setHasNextPage] = useState(false);
console.log('start-useData :>> ', start);
useEffect(() => {
setIsLoading(true);
setIsError(false);
setError({});
const controller = new AbortController();
const { signal } = controller;
getData(start, { signal })
.then((response) => {
setResult((prev) => [...prev, ...response.data]);
setHasNextPage(Boolean(response.data.length));
setIsLoading(false);
})
.catch((e) => {
setIsLoading(false);
if (signal.aborted) return;
setIsError(true);
setError({ "axios error:": e });
});
return () => controller.abort();
}, [start]);
return { result, error, isError, isLoading, hasNextPage };
}
父组件
import React, { useState, useCallback, useRef } from "react";
import useData from "./hooks/useData";
import Post from "./Post";
export default function CryptoList() {
const [start, setStart] = useState(0);
const { result, error, isError, isLoading, hasNextPage } = useData({ start });
console.log('start :>> ', start);
const intersectionObserver = useRef();
const lastDataRef = useCallback(
(data) => {
if (isLoading) return;
if (intersectionObserver.current)
intersectionObserver.current.disconnect();
intersectionObserver.current = new IntersectionObserver((entry) => {
if (entry[0].isIntersecting && hasNextPage)
setStart((prev) => prev + 10);
});
if (data) intersectionObserver.current.observe(data);
},
[hasNextPage, isLoading]
);
if (isError) return <h1>Error: {error.message}</h1>;
const content = result.map((data, index) => {
if (result.length === index + 1)
return <Post key={data.id} data={data} ref={lastDataRef} />;
return <Post key={data.id} data={data} />;
});
return (
<>
<h1>List of Crypto</h1>
{content}
{isLoading && <h2>Loading ...</h2>}
</>
);
}
控制台
我希望随着状态的变化,自定义钩子会重新渲染。
请注意,以上内容是代码的翻译部分,不包括问题或其他内容。如果您需要进一步的帮助,请随时提问。
<details>
<summary>英文:</summary>
I'm new and may have said the terms wrong
I tried to write an infinite scroll from the code below
[github](https://github.com/gitdagray/react_infinite_scroll)
By running the app, a few basic items are taken from the API, and when I scroll down, *start* changes, but a new request to get data is not sent.
my custom hook
import { useEffect, useState } from "react";
import { getData } from "../apis/coinmarketcapApi";
export default function useData({ start = 0 }) {
const [result, setResult] = useState([]);
const [error, setError] = useState({});
const [isError, setIsError] = useState(false);
const [isLoading, setIsLoading] = useState(false);
const [hasNextPage, setHasNextPage] = useState(false);
console.log('start-useData :>> ', start);
useEffect(() => {
setIsLoading(true);
setIsError(false);
setError({});
const controller = new AbortController();
const { signal } = controller;
getData(start, { signal })
.then((response) => {
setResult((prev) => [...prev, ...response.data]);
setHasNextPage(Boolean(response.data.length));
setIsLoading(false);
})
.catch((e) => {
setIsLoading(false);
if (signal.aborted) return;
setIsError(true);
setError({ "axios error:": e });
});
return () => controller.abort();
}, [start]);
return { result, error, isError, isLoading, hasNextPage };
}
parent component
import React, { useState, useCallback, useRef } from "react";
import useData from "./hooks/useData";
import Post from "./Post";
export default function CryptoList() {
const [start, setStart] = useState(0);
const { result, error, isError, isLoading, hasNextPage } = useData(start);
console.log('start :>> ', start);
const intersectionObserver = useRef();
const lastDataRef = useCallback(
(data) => {
if (isLoading) return;
if (intersectionObserver.current)
intersectionObserver.current.disconnect();
intersectionObserver.current = new IntersectionObserver((entry) => {
if (entry[0].isIntersecting && hasNextPage)
setStart((prev) => prev + 10);
});
if (data) intersectionObserver.current.observe(data);
},
[hasNextPage, isLoading]
);
if (isError) return <h1>Error: {error.message}</h1>;
const content = result.map((data, index) => {
if (result.length === index + 1)
return <Post key={data.id} data={data} ref={lastDataRef} />;
return <Post key={data.id} data={data} />;
});
return (
<>
<h1>List of Crypto</h1>
{content}
{isLoading && <h2>Loading ...</h2>}
</>
);
}
console
[enter image description here](https://i.stack.imgur.com/syJEm.png)
i want to with change of state, custom hook re-render
</details>
# 答案1
**得分**: 0
useData 钩子将仅在组件创建时调用一次。该钩子函数不会在每次重新渲染时运行。为了实现您期望的功能,请在钩子内部创建另一个状态,并公开 setter 函数。通过这样做,您可以更新钩子内部的 start 状态,从而触发 useEffect。
```jsx
import { useEffect, useState } from "react";
import { getData } from "../apis/coinmarketcapApi";
export default function useData() {
const [result, setResult] = useState([]);
const [error, setError] = useState({});
const [isError, setIsError] = useState(false);
const [isLoading, setIsLoading] = useState(false);
const [hasNextPage, setHasNextPage] = useState(false);
const [start, setStart] = useState(0);
console.log('start-useData :>> ', start);
useEffect(() => {
setIsLoading(true);
setIsError(false);
setError({});
const controller = new AbortController();
const { signal } = controller;
getData(start, { signal })
.then((response) => {
setResult((prev) => [...prev, ...response.data]);
setHasNextPage(Boolean(response.data.length));
setIsLoading(false);
})
.catch((e) => {
setIsLoading(false);
if (signal.aborted) return;
setIsError(true);
setError({ "axios error:": e });
});
return () => controller.abort();
}, [start]);
return { result, error, isError, isLoading, hasNextPage, setStart };
}
现在您可以在父组件的 useEffect 中使用 setStart。
英文:
The useData hook will be called only once when the component will be created. The hook function does not run on every rerender. To achieve your desired functionality, create another state inside your hook, and expose the setter function. By doing this, you can update the start state inside the hook and thus trigger useEffect.
<code>
import { useEffect, useState } from "react";
import { getData } from "../apis/coinmarketcapApi";
export default function useData() {
const [result, setResult] = useState([]);
const [error, setError] = useState({});
const [isError, setIsError] = useState(false);
const [isLoading, setIsLoading] = useState(false);
const [hasNextPage, setHasNextPage] = useState(false);
const [start, setStart] = useState(0);
console.log('start-useData :>> ', start);
useEffect(() => {
setIsLoading(true);
setIsError(false);
setError({});
const controller = new AbortController();
const { signal } = controller;
getData(start, { signal })
.then((response) => {
setResult((prev) => [...prev, ...response.data]);
setHasNextPage(Boolean(response.data.length));
setIsLoading(false);
})
.catch((e) => {
setIsLoading(false);
if (signal.aborted) return;
setIsError(true);
setError({ "axios error:": e });
});
return () => controller.abort();
}, [start]);
return { result, error, isError, isLoading, hasNextPage, setStart };
}
</code>
Now you can use setStart inside a useEffect in the parent component
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论