I get the console log to fire inside the hook on each resize however the console log that's in the callback that gets passed into the resize hook never fires.
Here is the hook i have which uses useCallbacks per the exhaustive deps eslint plugin:
// #flow
import { useCallback, useEffect } from 'react';
let resizeId: AnimationFrameID;
export const useResize = (callbackFn: Function, ref?: any) => {
const resizeUpdate = useCallback(() => {
console.log('inside');
callbackFn();
}, [callbackFn]);
const handleResize = useCallback(() => {
cancelAnimationFrame(resizeId);
resizeId = requestAnimationFrame(resizeUpdate);
}, [resizeUpdate]);
const handleOrientation = useCallback(() => {
// After orientationchange, add a one-time resize event
const afterOrientationChange = () => {
handleResize();
// Remove the resize event listener after it has executed
window.removeEventListener('resize', afterOrientationChange);
};
window.addEventListener('resize', afterOrientationChange);
}, [handleResize]);
useEffect(() => {
if (typeof ResizeObserver === 'function' && ref && ref.current) {
let resizeObserver = new ResizeObserver(() => handleResize());
resizeObserver.observe(ref.current);
return () => {
if (!resizeObserver) {
return;
}
resizeObserver.disconnect();
resizeObserver = null;
};
} else {
window.addEventListener('resize', handleResize);
window.addEventListener('orientationchange', handleOrientation);
return () => {
window.removeEventListener('resize', handleResize);
window.removeEventListener('orientationchange', handleOrientation);
cancelAnimationFrame(resizeId);
};
}
}, [callbackFn, handleOrientation, handleResize, ref]);
};
export default useResize;
Here is how you use it within a component:
...
const setContentRef = () => {
console.log('resize');
};
useResize(setContentRef);
...
Related
I try to make lazy loading for my products list using React and Redux. The problem is that I can't removeEventListener after all products are loaded.
all_loaded tells me if are products are loaded (true) or not (false).
So after the all_loaded changed to true, useEffect run code inside else but eventListener still exist after that.
const { all_loaded } = useAppSelector((state) => state.productsSlice);
const bottomScrollDetection = () => {
const position = window.scrollY;
var limit = document.body.offsetHeight - window.innerHeight;
if (position === limit) {
dispatch(fetchProducts(true));
}
};
useEffect(() => {
dispatch(fetchProducts(false));
if (!all_loaded) {
document.addEventListener("scroll", bottomScrollDetection);
} else {
document.removeEventListener("scroll", bottomScrollDetection);
}
}, [all_loaded]);
On the next re-render, a new function will be affected to bottomScrollDetection, the removeEventListener call will not remove the initial listener.
You can use the cleanup function :
useEffect(() => {
if (!all_loaded) {
dispatch(fetchProducts(false));
document.addEventListener("scroll", bottomScrollDetection);
return () => document.removeEventListener("scroll", bottomScrollDetection);
}
}, [all_loaded]);
When running the below code that gets the screen width of the browser I get this error:
ReferenceError: window is not defined
The code commented-out works, uses 0 as a default, but correctly updates when the browser width is changed.
I either get the window is not defined error or pass a default number to state, which displays the incorrect width on page load.
How do I define window.innerWidth at page load?
import React, { useState, useEffect } from "react";
const WindowTracker = () => {
const [show, setShow] = useState(true);
// const [windowWidth, setWindowWidth] = useState(0);
const [windowWidth, setWindowWidth] = useState(window.innerWidth);
const triggerToggle = () => {
setShow(!show);
};
useEffect(() => {
function watchWidth() {
setWindowWidth(window.innerWidth);
}
window.addEventListener("resize", watchWidth);
return function () {
window.removeEventListener("resize", watchWidth);
};
}, []);
return (
<div>
<button onClick={triggerToggle}>Toggle WindowTracker</button>
{show && <h1>Window width: {windowWidth}px</h1>}
</div>
);
// }
};
export default WindowTracker;
Thanks!
This happens because in the pre rendering that nextjs performs on the window server it is not yet defined, I solved this in my code by adding an if, this is an example code of how it would look
import React, { useState, useEffect } from "react";
const WindowTracker = () => {
const [show, setShow] = useState(true);
// const [windowWidth, setWindowWidth] = useState(0);
const [windowWidth, setWindowWidth] = useState(typeof window !== "undefined" ? window.innerWidth : 0);
const triggerToggle = () => {
setShow(!show);
};
useEffect(() => {
if (typeof window !== "undefined") {
function watchWidth() {
setWindowWidth(window.innerWidth);
}
window.addEventListener("resize", watchWidth);
return function () {
window.removeEventListener("resize", watchWidth);
};
}
}, []);
return (
<div>
<button onClick={triggerToggle}>Toggle WindowTracker</button>
{show && <h1>Window width: {windowWidth}px</h1>}
</div>
);
// }
};
export default WindowTracker;
Use this Hook :
const [windowWidth, setWindowWidth] = useState();
useEffect(() => {
setWindowWidth(window.innerWidth)
}, [window.innerWidth])
You should trigger setWindowWidth when component did mount
const isClientSide = () => typeof window !== 'undefined';
const getWindowSize = () => {
if (isClientSide()) return window.innerWidth
return 0
}
const WindowTracker = () => {
const [show, setShow] = useState(true);
const [windowWidth, setWindowWidth] = useState(getWindowSize());
const triggerToggle = () => {
setShow(!show);
};
useEffect(() => {
function watchWidth() {
setWindowWidth(window.innerWidth);
}
window.addEventListener("resize", watchWidth);
setWindowSize(getWindowSize()); <=== This should set the right width
return function () {
window.removeEventListener("resize", watchWidth);
};
}, []);
return (
<div>
<button onClick={triggerToggle}>Toggle WindowTracker</button>
{show && <h1>Window width: {windowWidth}px</h1>}
</div>
);
};
export default WindowTracker;
Inside useEffect hook you can also invoke setWindowWidth(window.innerWidth); at to set the state with window.innerWidth to have desired width on pageLoad.
const [windowWidth, setWindowWidth] = useState(0);
useEffect(() => {
function watchWidth() {
setWindowWidth(window.innerWidth);
}
window.addEventListener("resize", watchWidth);
// Call right away so state gets updated with initial window size
setWindowWidth(window.innerWidth);
return function () {
window.removeEventListener("resize", watchWidth);
};
}, []);
example:
resize using react js
this is my code:
import React, { useState, useEffect } from 'react';
const getWidthWindow = () => {
const [widthWindow, setWidthWindow] = useState(null)
const updateDimensions = () => {
setWidthWindow(window.screen.width)
}
useEffect(() => {
console.log(widthWindow)
setWidthWindow(window.screen.width)
updateDimensions()
window.addEventListener('resize', updateDimensions)
return () => window.removeEventListener('resize', updateDimensions)
}, [widthWindow])
}
export default getWidthWindow;
I want to get the window width value but the result is like it doesn't match the window size so how to fix it?
Your code is correct but the logging isn't.
Add a hook to log the dimensions when it updates:
useEffect(() => {
console.log(windowDimensions)
}, [windowDimensions])
Working codesandbox.
I go with the above answer of adding windowDimensions to the useEffect's Dependency array but I like to add up little sugar on top of it..
On Resize, the event gets triggered continuously and impacts performance a bit..
So, I have implemented throttling to improve the performance..
Answer for your updated question: Stackblitz link
const GetWidthWindow = () => {
const [widthWindow, setWidthWindow] = useState(window.innerWidth);
useEffect(() => {
let throttleResizeTimer = null;
function handleResize() {
clearTimeout(throttleResizeTimer);
throttleResizeTimer = setTimeout(
() => setWidthWindow(window.innerWidth),
500
);
}
window.addEventListener('resize', handleResize);
return () => window.removeEventListener('resize', handleResize);
}, [widthWindow]);
return <p>{JSON.stringify(widthWindow)}</p>;
};
export default GetWidthWindow;
Answer for your old question:
useEffect(() => {
// implement throttle for little performance gain
let throttleResizeTimer = null;
function handleResize() {
clearTimeout(throttleResizeTimer);
throttleResizeTimer = setTimeout(
() => setWindowDimensions(getWindowDimensions()),
500
);
}
window.addEventListener('resize', handleResize);
return () => window.removeEventListener('resize', handleResize); }, [windowDimensions]);
I'm creating a custom React hook. The scrolling target element can either be the myref.current or the window object.
If it's the myref.current target value I want to use the scrollTop method, if it's the window object I want to use pageYOffset method (since scrollTop method isn't available for the window).
How do I create this function and write this in a clean way?
const useMyHook = (target: 'ref' | 'window') => {
const [fade, setFade] = useState(false);
const myRef = useRef<HTMLDivElement>(null);
// useEffect(() => {
// const onScroll = () => {
// if (window.pageYOffset > 50) {
// setFade(true);
// } else {
// setFade(false);
// }
// };
// window.addEventListener('scroll', onScroll);
// return () => window.removeEventListener('scroll', onScroll);
// }, []);
useEffect(() => {
const node = target === 'ref' ? scrollRef.current : window;
const onScroll = () => {
if (node) {
if (node.scrollTop > 50) {
setFade(true);
} else {
setFade(false);
}
}
};
if (node) {
node.addEventListener('scroll', onScroll);
}
return () => node?.removeEventListener('scroll', onScroll);
}, []);
return { myRef, fade };
};
export default useMyHook;
I would have implemented this way:
// node - any element you want to add event listener to, For eg: scrollRef.current or the window object
const useMyHook = (node) => {
const [fade, setFade] = useState(false);
useEffect(() => {
if (!node) return
const onScroll = () => {
const verticalScroll = node === window ? node.pageYOffset : node.scrollTop;
if (verticalScroll > 50) {
setFade(true);
} else {
setFade(false);
}
};
node.addEventListener('scroll', onScroll);
return () => node.removeEventListener('scroll', onScroll);
}, [node]);
return fade;
};
export default useMyHook;
PS: Not sure why you used myRef.
This is the cleanest in my opinion (also removed myRef as its not in used):
const useMyHook = node => {
const [fade, setFade] = useState(false);
useEffect(() => {
if (node){
const verticalScroll = node === window ? node.pageYOffset : node.scrollTop;
const onScroll = () => setFade(verticalScroll > 50);
node.addEventListener('scroll', onScroll);
}
return () => node?.removeEventListener('scroll', onScroll);
}, [node]);
return fade;
};
export default useMyHook;
The following is a component whose functionality, partly, is to change the window's title as the page is getting focused and blurred. It does not work.
const ATitleChangingComponent = () => {
const focusFunction = (event: FocusEvent) => {
document.title = "focused";
};
const blurFunction = (event: FocusEvent) => {
document.title = "blurred";
};
useEffect(() => {
window.addEventListener("focus", focusFunction);
return window.removeEventListener("focus", focusFunction);
}, []);
useEffect(() => {
window.addEventListener("blur", blurFunction);
return window.removeEventListener("blur", blurFunction);
}, []);
return <p>some unimportant jsx</p>
};
However,
const focusFunction = (event: FocusEvent) => {
document.title = "focused";
};
window.addEventListener("focus", focusFunction);
works just fine.
A side question: are const focusFunction and const blurFunction getting constructed within the function each render? I assume if so, they should be lifted out of the component to avoid unnecessary overhead?
Need to return a function, otherwise listener is removed immediately.
The function gets called when the component unmounts
useEffect(() => {
window.addEventListener("blur", blurFunction);
return () => window.removeEventListener("blur", blurFunction);
}, []);