问题
I have a hook that listens to the window.resize
event.
I wish to only listen to and update when window.innerWidth
changes. I wish to ignore changes to window.innerHeight
since this gets triggered when opening the soft keyboard.
The problem is that the mediaSize is stored in my Redux Store causing the app to re-render when the soft keyboard opens.
My code triggers on both window.innerWidth
and window.innerHeight
changes, how can I change this behaviour?
My hook
import { useState, useEffect } from 'react';
// Hook
export const useWindowSize = () => {
const isClient = typeof window === 'object'; //Object represents browser window
function getSize() {
return {
width: isClient ? window.innerWidth : undefined
}
}
const [windowSize, setWindowSize] = useState(getSize)
useEffect(() => {
if (!isClient) { return false } //Exit if not user/browser
function handleResize() {
setWindowSize(getSize())
}
window.addEventListener('resize', handleResize) // <-- I am only interested in window.innerWidth !
return () => window.removeEventListener('resize', handleResize)
}, []) // Empty array ensures that effect is only run on mount and unmount
return windowSize
}
Implementation in my AppRouter.js
const AppRouter = ({ isNavOpen, mediaSize, startSetMediaSize, language, ...rest }) => {
//** Start mediaQuery */
const mediaBreakpoints = {
smallStr: styles['breakpoint-small-value'],
mediumStr: styles['breakpoint-medium-value'],
largeStr: styles['breakpoint-large-value'],
smallInt: new Number(styles['breakpoint-small-value']).valueOf(),
mediumInt: new Number(styles['breakpoint-medium-value']).valueOf(),
largeInt: new Number(styles['breakpoint-large-value']).valueOf(),
small: styles['breakpoint-small'],
medium: styles['breakpoint-medium'],
large: styles['breakpoint-large']
}
//Calculate media size
const screenWidth = useWindowSize().width
...
useEffect(() => {
if (screenWidth > mediaBreakpoints.largeInt) {
if (mediaSize !== MEDIA_LARGE) { startSetMediaSize(MEDIA_LARGE) }
} else if (screenWidth > mediaBreakpoints.mediumInt) {
if (mediaSize !== MEDIA_MEDIUM) { startSetMediaSize(MEDIA_MEDIUM) }
} else if (screenWidth > mediaBreakpoints.smallInt) {
if (mediaSize !== MEDIA_SMALL) { startSetMediaSize(MEDIA_SMALL) }
} else {
//Tiny and small are treated equally
if (mediaSize !== MEDIA_SMALL) { startSetMediaSize(MEDIA_SMALL) }
}
}, [screenWidth]) // Only run if screen width changes
}
const mapStateToProps = (state) => ({
isNavOpen : state.ui.isOpen,
mediaSize: state.ui.mediaSize, //small / medium / large
language: state.usersettings.language //se||en
})
const mapDispatchToProps = (dispatch) => ({
startSetNavigationState: (isOpen) => dispatch(setNavigationState(isOpen)),
startSetMediaSize: (mediaSize) => dispatch(setMediaSize(mediaSize))
})
//export default AppRouter
export default connect(mapStateToProps, mapDispatchToProps)(AppRouter)
My mount point for AppRouter
const jsx = (
<React.StrictMode>
<Provider store={store}>
<AppRouter />
</Provider>
</React.StrictMode>
)
Kind regards /K
回答1:
A simple solution I can think of, is to cache the last innerWidth and only perform your resize logic if it changed. Something like this:
import { useState, useEffect, useRef } from 'react';
// Hook
export const useWindowSize = () => {
const isClient = typeof window === 'object'; //Object represents browser window
const lastWidth = useRef();
function getSize() {
return {
width: isClient ? window.innerWidth : undefined
}
}
const [windowSize, setWindowSize] = useState(getSize)
useEffect(() => {
if (!isClient) { return false } //Exit if not user/browser
function handleResize() {
if (window?.innerWidth !== lastWidth.current) {
const width = getSize();
lastWidth.current = width;
setWindowSize(width)
}
}
window.addEventListener('resize', handleResize) // <-- I am only interested in window.innerWidth !
return () => window.removeEventListener('resize', handleResize)
}, []) // Empty array ensures that effect is only run on mount and unmount
return windowSize
}
来源:https://stackoverflow.com/questions/64411993/react-hook-only-listen-to-window-width-size-change