I'm using matchMedia in React to collapse my SideBar when the page is resizing. But the problem is if I refresh the page, my sidebar is open not closed. So if I want to collapse my SideBar I need to resize the page again or use the close button.
const layout = document.getElementById('home-layout');
const query = window.matchMedia('(max-width: 765px)');
query.onchange = (evt) => {
if( query.matches ) {
changeMenuMinified(true);
layout.classList.add('extended-layout');
}
else {
changeMenuMinified(false);
layout.classList.remove('extended-layout');
}
};
query.onchange();
};
useEffect(() => {
window.addEventListener('resize', handleResize);
});
If I remove addEventListener it works, I can reload the page and my sidebar stays closed but if I try to open the sidebar with a button, the sidebar closes quickly
const handleResize = () => {
const layout = document.getElementById('home-layout');
const query = window.matchMedia('(max-width: 765px)');
query.onchange = (evt) => {
if( query.matches ) {
changeMenuMinified(true);
layout.classList.add('extended-layout');
}
else {
changeMenuMinified(false);
layout.classList.remove('extended-layout');
}
};
query.onchange();
};
useEffect(() => {
handleResize()
});
sideBar
Some stuff to consider here:
Initialize your state with the current matching value
Remove listener on effect cleanup function
Don't forget the useEffect dependency array to avoid your code being executed on each render.
You can find a working example here -> https://codesandbox.io/s/stack-72619755-lpwh6m?file=/src/index.js:0-613
const query = window.matchMedia('(max-width: 765px)')
const App = () => {
const [minified, changeMenuMinified] = useState(query.matches)
useEffect(() => {
const resizeHandler = () => {
if (query.matches) {
changeMenuMinified(true)
} else {
changeMenuMinified(false)
}
}
query.addEventListener("change", resizeHandler);
return () => query.removeEventListener("change", resizeHandler);
})
return <p>{minified ? 'minified' : 'expanded'}</p>
}
That's because you need to have both in order to work, on load and also on reside, for that you can just do so:
Notice I added that empty dependencies array.
useEffect(() => {
handleResize();
window.addEventListener('resize', handleResize);
},[]);
Related
I've am creating a react app and I wanna stop navigation to another page if my form is left dirty
import { useEffect, useState } from "react";
const unsavedChanges = () => {
const [isDirty, setDirty] = useState(false);
useEffect(() => {
function confirmNavigation(event) {
if (isDirty) {
event.preventDefault();
event.returnValue = "Are you sure you want to leave? Your changes will be lost."; // eslint-disable-line no-param-reassign
}
}
window.addEventListener("beforeunload", confirmNavigation);
return () => {
window.removeEventListener("beforeunload", confirmNavigation);
};
}, [isDirty]);
const onDirty = () => setDirty(true);
const onPristine = () => setDirty(false);
return { onDirty, onPristine };
};
export default unsavedChanges;
The code I wrote lets me not reload the page if my form is dirty but I can't prevent it from navigating to another page since react doesn't load the whole page it just loads the data to be changed. I can't use Prompt, useHistory, or useBlocking because they don't exist in react-router v6.4.4.
How can I achieve that?
Why not register a window.onbeforeunload listener? See https://stackoverflow.com/a/1119324/9824103
const unsavedChanges = () => {
const [isDirty, setDirty] = useState(false);
useEffect(() => {
function confirmNavigation(event) {
return isDirty ? "Are you sure you want to leave? Your changes will be lost." : null; // eslint-disable-line no-param-reassign
}
window.onbeforeunload = confirmNavigation;
return () => {
window.beforeunload = null;
};
}, [isDirty]);
const onDirty = () => setDirty(true);
const onPristine = () => setDirty(false);
return { onDirty, onPristine };
};
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]);
Basically the same question as How to cancel a javascript function if the user scrolls the page but using react hooks.
I wrote react code that scrolls down to the end of the page after 3 seconds.
const scrollToEnd = () => { /* implementation omitted */ }
useEffect(() => {
const id = setTimeout(() => scrollToEnd(), 3000)
return () => clearTimeout(id)
}, [])
I want modify this code so that if the user manually scrolls the page before this timeout, the timeout is cleared.
I was thinking of a solution like:
const [hasScrolled, setHasScrolled] = useState(false);
const scrollToEnd = () => { /* implementation omitted */ }
useEffect(() => {
const setHasScrolledCallback = () => setHasScrolled(true)
window.addEventListener("scroll", setHasScrolledCallback);
return () => window.removeEventListener("scroll", setHasScrolledCallback);
}, []);
useEffect(() => {
const scrollCallback = () => { if (hasScrolled) scrollToEnd() }
const id = setTimeout(scrollCallback, 3000)
return () => clearTimeout(id)
}, [])
This works, but I don't think this is the correct way to approach this problem, because the scroll event is fired multiple times, even after the timeout occurs. Also the scrollCallback isn't really canceled, it runs anyway even if it does nothing.
I need a way to distinguish manual scroll from programmatically called el.scrollIntoView()
I tried to google it and they have some suggestion like to use wheel event and similar, but that is not solving the problem as it is not covering every scroll type ( for example it is not including manual scroll when you drag and drop scrollBar)
Here is the code for better context:
const [autoScrollEnabled, setAutoScrollEnabled] = useState(true);
const autoScrollDelay: any = useRef(null);
useAutoScroll(autoScrollEnabled);
const pauseAutoScroll = () => {
if (autoScrollEnabled) {
setAutoScrollEnabled(false);
}
clearTimeout(autoScrollDelay.current);
autoScrollDelay.current = setTimeout(() => {
setAutoScrollEnabled(true);
}, 3000);
};
const onScroll = useCallback(
event => {
if (isPlaying) {
pauseAutoScroll();
}
},
[isPlaying, autoScrollEnabled],
);
useEffect(() => {
const list = document.querySelector(
"[data-testid='#panel-layout/content']",
);
list?.addEventListener('scroll', onScroll);
return () => {
list?.removeEventListener('scroll', onScroll);
clearTimeout(autoScrollDelay?.current);
};
}, [onScroll]);
Autoscroll hook
export const useAutoScroll = (enabled = true) => {
const currentTime = useSelector(state => state.timeline.currentTime);
const isPlaying = useSelector(state => state.timeline.isPlaying);
const shouldAutoScroll = currentTime && enabled && isPlaying;
useEffect(() => {
if (shouldAutoScroll) {
const currElUuid = calculateUuid();
const el = document.querySelector(`[data-uuid="${currElUuid}"]`);
if (el) {
el.scrollIntoView({ behavior: 'smooth', block: 'end' });
}
}
}, [currentTime, enabled, isPlaying, subs, lastHighlightedUuid]);
};
And then in custom hook I am calling
el.scrollIntoView({ behavior: 'smooth', block: 'end' });
, which triggers the same scroll listener as the manual one, and all the time calls my pauseAutoScroll() method which I need to prevent from being called by scrollIntoView.
Would really appreciate your help 🙌
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]);