i've implemented infinite scroll hook for my newspage
const [posts, setPosts] = useState([]);
const [currentOffset, setCurrentOffset] = useState(0);
const [isLoading, setLoading] = useState(true);
const [isFetching, setIsFetching] = useState(false);
let loadThreePosts = () => {
axios.get(`/news?limit=3&offset=${currentOffset}`).then(({ data }) => {
let threePosts = [];
console.log(data);
data.data.forEach((p) => threePosts.push(p));
setPosts((posts) => [...posts, ...threePosts]);
setLoading(false);
});
setCurrentOffset(currentOffset + 3);
};
useEffect(() => {
window.addEventListener("scroll", handleScroll);
return () => window.removeEventListener("scroll", handleScroll);
}, []);
useEffect(() => {
if (!isFetching) return;
fetchMorePosts();
}, [isFetching]);
function handleScroll() {
if (
window.innerHeight + document.documentElement.scrollTop !==
document.documentElement.offsetHeight ||
isFetching
)
return;
setIsFetching(true);
}
function fetchMorePosts() {
setTimeout(() => {
loadThreePosts();
setIsFetching(false);
}, 2000);
}
So if i go to the newspage throw the Link on my site it fetching posts as i need,
but if i paste newspage link in browser address bar it's not fetch anything
Change your code to this:
useEffect(() => {
window.addEventListener("scroll", handleScroll);
handleScroll(); // Call handleScroll function
return () => window.removeEventListener("scroll", handleScroll);
}, []);
Related
I don't know why the infinite scroll made of react doesn't work.
Here's what I think.
Connected Element Detection
isFetching = true
setTest()
const [isFetching, setIsFetching] = useState(false);
useEffect(() => {
const io = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
setIsFetching(true);
}
});
setIsFetching(false);
});
io.observe(document.getElementById("finish")!);
return () => io.disconnect();
}, []);
useEffect(() => {
if (!isFetching) return;
setTest([...test, 1]);
}, [isFetching, test]);```
I have this hook in React I want to write the unit test but I faced the problem that I don't know how I could cover handleScroll function, how can I go to the useEffect to trigger the scroll event?
I tried fireEvent.scroll but not success.
import { useState, useEffect } from 'react';
const scrollWindow = (
fetchOffset: number,
callback: () => void,
preventFetch?: boolean,
) => {
const [isFetching, setIsFetching] = useState(false);
const handleScroll = () => {
const scrollFromBottom = docElem.scrollHeight - docElem.scrollTop - docElem.clientHeight;
if (scrollFromBottom - fetchOffset > 0) return;
setIsFetching(true);
};
useEffect(() => {
window.addEventListener('scroll', handleScroll);
return () => window.removeEventListener('scroll', handleScroll);
}, [isFetching, preventFetch]);
useEffect(() => {
if (!isFetching) return;
callback();
}, [isFetching]);
return [isFetching, setIsFetching];
};
export default scrollWindow;
There is a simple code where i make the api call with react redux.
But there is one more thing. periodically i increase the progress value I show on the screen. I use useEffect for this. but when i increase progress, api goes back to call. I just want to make my api call once.
here is an example of my code
const Do = () => {
const [progress, setProgress] = useState(1);
const dispatch = useDispatch();
dispatch(myApiCall);
useEffect(() => {
const interval = setInterval(() => {
setProgress(progress => progress + 10);
}, 1500);
return () => clearInterval(interval);
}, [progress]);
return (
<ProgressBar
completed={progress}
/>
);
};
You only need to call the API in useEffect too:
const Do = () => {
const [progress, setProgress] = useState(1);
const dispatch = useDispatch();
useEffect(() => {
dispatch(myApiCall);
}, [dispatch]);
useEffect(() => {
const interval = setInterval(() => {
setProgress((progress) => progress + 10);
}, 1500);
return () => clearInterval(interval);
}, []);
return <ProgressBar completed={progress} />;
};
You need to call your api call inside of a componentDidMount equivalent useEffect:
const Do = () => {
const [progress, setProgress] = useState(1);
const dispatch = useDispatch();
useEffect(() => {
dispatch(myApiCall);
}, []);
useEffect(() => {
const interval = setInterval(() => {
setProgress(progress => progress + 10);
}, 1500);
return () => clearInterval(interval);
}, [progress]);
return (
<ProgressBar
completed={progress}
/>
);
};
I have hook useInterval which download data every 10 seconds automaticaly, however I have also button which can manually download data in every moment. I'm struggling to restart interval timer when I click button. So basically if interval counts to 5, but I click button meantime, interval should restart and starts counting to 10 again before downloading data
const useInterval = (callback, delay) => {
const savedCallback = useRef(callback);
useEffect(() => {
savedCallback.current = callback;
}, [callback]);
useEffect(() => {
const tick = () => {
savedCallback.current();
}
if (delay !== null) {
const id = setInterval(tick, delay);
return () => clearInterval(id);
}
}, [delay]);
};
export default useInterval;
APP PART:
useInterval(() => {
getMessage();
}, 10000)
const getMessage = async () => {
setProcessing(true)
try {
const res = await fetch('url')
const response = await res.json();
setRecievedData(response)
}
catch (e) {
console.log(e)
}
finally {
setProcessing(false)
}
}
const getMessageManually = () => {
getMessage()
RESTART INTERVAL
}
You can add a reset function in the hook and return that function. The reset function should clear the existing interval and start a new one.
Here is the code for the hook which can be reset and stopped.
const useInterval = (callback, delay) => {
const savedCallback = useRef(callback);
const intervalRef = useRef(null);
useEffect(() => {
savedCallback.current = callback;
}, [callback]);
useEffect(() => {
if (delay !== null) {
const id = setInterval(savedCallback.current, delay);
intervalRef.current = id;
return () => clearInterval(id);
}
}, [delay]);
useEffect(()=>{
// clear interval on when component gets removed to avoid memory leaks
return () => clearInterval(intervalRef.current);
},[])
const reset = useCallback(() => {
if(intervalRef.current!==null){
clearInterval(intervalRef.current);
intervalRef.current = setInterval(savedCallback.current,delay)
}
});
const stop = useCallback(() => {
if(intervalRef.current!==null){
clearInterval(intervalRef.current);
}
})
return {
reset,
stop
};
};
// usage
const {reset,stop} = useInterval(()=>{},10000);
reset();
stop();
You should add a reset function as returning a value from the hook.
I also fixed few issues and added an unmount handler:
// Usage
const resetInterval = useInterval(() => ..., DELAY);
resetInterval();
// Implementation
const useInterval = (callback, delay) => {
const savedCallbackRef = useRef(callback);
const intervalIdRef = useRef();
useEffect(() => {
savedCallback.current = callback;
}, [callback]);
// handle tick
useEffect(() => {
const tick = () => {
savedCallback.current();
};
if (delay !== null) {
intervalIdRef.current = setInterval(tick, delay);
}
const id = intervalIdRef.current;
return () => {
clearInterval(id);
};
}, [delay]);
// handle unmount
useEffect(() => {
const id = intervalIdRef.current;
return () => {
clearInterval(id);
};
}, []);
const resetInterval = useCallback(() => {
clearInterval(intervalIdRef.current);
intervalIdRef.current = setInterval(savedCallback.current, delay)
}, [delay]);
return resetInterval;
};
Another solution is to remove the ref on the callback making the hook restart the count on every change to the callback
so updating the above solution
// Implementation
const useInterval = (callback, delay) => {
const intervalIdRef = useRef();
// handle tick
useEffect(() => {
const tick = () => {
callback();
};
if (delay !== null) {
intervalIdRef.current = setInterval(tick, delay);
}
const id = intervalIdRef.current;
return () => {
clearInterval(id);
};
}, [delay]);
// handle unmount
useEffect(() => {
const id = intervalIdRef.current;
return () => {
clearInterval(id);
};
}, []);
};
And then you can use it like this
const [counter, setCounter] = useState[0]
const onTimerFinish = useCallback(() => {
setCounter(counter + 1)
// setCounter will reset the interval
}, [counter])
useResetInterval(() => {
onTimerFinish()
}, 5000)
So basically, I'm trying to fetch data from api and pass it to Component.
I create usePosition hook to get my positon from browser, and then get response from api. I really don't know how to wait with useEffect for my position, when i'm executing this code now I'm getting always log 'no position'.
const usePosition = () => {
const [error, setError] = useState(null);
const [position, setPosition] = useState();
useEffect(() => {
const geo = navigator.geolocation;
if(!geo) {
setError('Geolocation is not supported.');
return;
}
const handleSuccess = position => {
const { latitude, longitude } = position.coords;
setPosition({
latitude,
longitude
});
};
const handleError = error => {
setError(error.message);
};
geo.getCurrentPosition(handleSuccess, handleError);
}, []);
return { position, error };
}
function App() {
const {position, error} = usePositon();
const [weather, setWeather] = useState([]);
useEffect(() => {
if(position) {
const URL = `https://api.openweathermap.org/data/2.5/onecall?lat=${position.latitude}&lon=${position.longitude}&exclude=current,minutely,daily&units=metric&lang=pl&appid=${API_KEY}`;
const fetchData = async () => {
const result = await fetch(URL)
.then(res => res.json())
.then(data => data);
setWeather(result.hourly);
}
fetchData();
} else {
console.log('no position');
}
}, []);
return (
<div className="App">
<div>
<Swiper weather={weather}/>
</div>
</div>
)
}
It's all because of [] empty dependencies list down in App's useEffect. It runs exactly once on mount, when usePosition has not requested anything yet. And once it successes later and returns different { error, position } App does not react.
How to solve? Provide things as dependencies:
useEffect(() => {
if(position) {
const URL = `https://api.openweathermap.org/data/2.5/onecall?lat=${position.latitude}&lon=${position.longitude}&exclude=current,minutely,daily&units=metric&lang=pl&appid=${API_KEY}`;
const fetchData = async () => {
const result = await fetch(URL)
.then(res => res.json())
.then(data => data);
setWeather(result.hourly);
}
fetchData();
} else {
console.log('no position');
}
}, [position, error]);