I understand useInterval allows you to pass functional components as parameters, so I'm trying to take advantage of that and setting states with values used inside a functional component. In the following code, I want the ExecutionsPage to fetch projectScans and isProjectScansFetchComplete, which are returned from the useFetchProjectScansByUser() hook as an array:
1 const ExecutionsPage: React.FC = () => {
2 let user:string|null = sessionStorage.getItem('user')
3 const UPDATE_TABLE_TIMER: number = 30000; // Time (ms) until we call the API again to update the scans table
4 const [stateProjectScans, setStateProjectScans] = useState<Array<IProjectScan>>([]);
5 const [stateIsProjectScansFetchComplete, setStateIsProjectScansFetchComplete] = useState<boolean>(false);
6 function CallProjectScans(){
7 let [projectScans, isProjectScansFetchComplete] = useFetchProjectScansByUser(user)
8 return [projectScans, isProjectScansFetchComplete]
9 }
10 useEffect(() => {
11 let [projectScans, isProjectScansFetchComplete] = CallProjectScans()
12 // @ts-ignore
13 setStateProjectScans(projectScans)
14 // @ts-ignore
15 setStateIsProjectScansFetchComplete(isProjectScansFetchComplete)
16 }, [])
17 const GetProjectScans = () => {
18 let [projectScans, isProjectScansFetchComplete] = useFetchProjectScansByUser(user);
19 setStateProjectScans(projectScans)
20 };
21 // @ts-ignore
22 useInterval(GetProjectScans(), UPDATE_TABLE_TIMER)
In using this code, I'm getting this error: "Error: Too many re-renders. React limits the number of renders to prevent an infinite loop." So, with all that said, here are my questions:
Edit: Here's the function definition of useFetchProjectScansByUser():
export const useFetchProjectScansByUser = (user: string|null): [IProjectScan[], boolean] => {
const [projectScans, setProjectScans] = useState<IProjectScan[]>([]);
//update here
const [isProjectScansFetchComplete, setIsProjectScansFetchComplete] = useState<boolean>(false);
const {enqueueSnackbar, } = useSnackbar();
useEffect(() => {
...
/*function callJSONFunction(json) {
let projectScans: IProjectScan[] = createProjectScansFromJSON(json);
setProjectScans(projectScans);
}*/
const fetchProjectScans = async () => {
try {
const response = await fetch(URL);
if (!response.ok) throw response.statusText;
const json = await response.json();
let projectScans: IProjectScan[] = createProjectScansFromJSON(json);
setProjectScans(projectScans);
...
finally {
setIsProjectScansFetchComplete(true);
}
};
fetchProjectScans();
}
}, []);
return [projectScans, isProjectScansFetchComplete];
}
As expected, your hook uses state to store the results of the last call to the server.
If you want to modify/update that state, you should make your sever call in the useEffect
fire again by adding a new state variable and passing it to the dependency array. You can then pass back to the hook caller a way to update that dependency array, so in effect you can control the update from outside the hook.
This is exactly how hooks should work: compartmentalize logic, and expose a simple API to the consumers of the hook.
export const useFetchProjectScansByUser = (user: string|null): [IProjectScan[], boolean] => {
// make new state variable to control useEffect firing
const [update, setUpdate] = useState(0)
useEffect(() => {
console.log("I will fire every time the update var changes")
console.log("Instead of only on mount")
}, [update]) // pass the state var to dep array
// create a function that will update state reliably every call
const updateFunc = () => setUpdate((v) => v+1)
// pass it back to the hook consumer
return [X, Y, updateFunc]
}
const MyComponent = () => {
// now we have an update function to call from the consumer
const [X, Y, update] = useFetchProjectScansByUser('user')
useEffect(() => {
// call the update function every 3 seconds, which will re-run the hook useEffect
const id = setInterval(() => update(), 3000)
return () => clearInterval(id)
}, [])
// if a hook state updates, this component which consumes that hook will re-render
// thus X and Y will be updated.
return <div>{X} {Y}</div>
}