So I'm simply trying to toggle the visibility of a React component on a specific keypress.
This is working perfectly, but it seems every Time i toggle the component visibility using ctrl+`, the process gets slower and slower, and my console.log(e) from the onKey function in my app component gets called 10,50,100, 1000 times etc.
This is what I have so far
ReactDOM.render(
<React.StrictMode>
<Router>
<App />
</Router>
</React.StrictMode>,
document.getElementById('root')
);
function App() {
const [compHidden, toggleComp] = useState(true);
const onKey = (e) => {
console.log(e)
if(e.ctrlKey && e.key === '`') {
toggleComp(!compHidden);
}
}
document.addEventListener('keydown', onKey);
return (
<ComponentToHide hidden={compHidden}/>
);
}
function ComponentToHide(props) {
return (
<div style={{display: props.hidden ? 'none' : 'flex'}}>
<p>Visible</p>
</div>
)
}
Do not use document.addEventListener inside the render function, as that function is called every time your component renders and it will add a new event listner each time.
Instead, use the React Hook React.useEffect() to add the event listner only once as the component is mounted:
function App() {
const [compHidden, setCompHidden] = useState(true);
useEffect(() => {
const onKey = (e) => {
console.log(e)
if(e.ctrlKey && e.key === '`') {
setCompHidden(value => !value);
}
}
document.addEventListener('keydown', onKey);
return () => document.removeEventListener('keydown', onKey);
}, [])
return (
<ComponentToHide hidden={compHidden}/>
);
}
Two things to note:
useEffect's second argument, just an empty array, tells React only to run that function once at the beginning of the components lifecycle
You can return a cleanup function that is ran as your component is unmounted. We use this here to remove the event listener to fix an additional memory leak.
Related
Related to
How can I avoid unnecessary re rendering of an HOC component, when the parameter component renders in ReactJS with react router
HOC inside react hook, causes infinite rerenders (solves initial problem, but does not handle a specific case with the forwardRef)
ref from React.forwardRef is null but for HOC specifically and functional components
Given an app that's implemented as follows which triggers a state change every second:
export default function App() {
const [username, setUsername] = useState('');
const [now, setNow] = useState(format(Date.now(), 'PPPPpppp'));
useEffect(() => {
const c = setInterval(
() => setNow(format(Date.now(), 'PPPPpppp')),
1000
);
return () => clearInterval(c);
}, []);
return (
<View style={styles.container}>
<MyView>
<TextInput
placeholder="Username"
defaultValue={username}
onChangeText={setUsername}
/>
</MyView>
<Text>{now}</Text>
</View>
);
}
Given a simple
function MyView(props) {
return createElement(View, props);
}
The text input field does not lose focus when clicked
Now if I wrap it in an HOC with a useCallback to prevents infinite rerenders causing focus lost:
function withNothing(WrappedComponent) {
return useCallback((props) => <WrappedComponent {...props} />, [])
}
function MyView(props) {
return createElement(withNothing(View), props);
}
Now the last bit was to allow forwarding refs, but this is the part I couldn't get working.
My attempts so far https://snack.expo.dev/#trajano/hoc-and-rerender
Currently I'm doing this to close my topbar menu but it isn't what I need because it only closes when the menu icon is clicked.
It needs to close when I click anywhere on the website. Is there an easy and efficient way to achieve this with react?
Navbar.js
const handleToggle = () => {
setActive(!isActive);
};
<div className="account" onClick={handleToggle}><img src={davatar} className="avatar" alt="accountmenu" width="40" height="40" /></div>
There is a package called react-cool-onclickoutside. You can use that to solve this issue
OR you can u can create a custom useOutsideHook refer here
import { useState, useEffect, useRef } from "react";
// Usage
function App() {
// Create a ref that we add to the element for which we want to detect outside clicks
const ref = useRef();
// State for our modal
const [isModalOpen, setModalOpen] = useState(false);
// Call hook passing in the ref and a function to call on outside click
useOnClickOutside(ref, () => setModalOpen(false));
return (
<div>
{isModalOpen ? (
<div ref={ref}>
Hey, I'm a modal. Click anywhere outside of me to close.
</div>
) : (
<button onClick={() => setModalOpen(true)}>Open Modal</button>
)}
</div>
);
}
// Hook
function useOnClickOutside(ref, handler) {
useEffect(
() => {
const listener = (event) => {
// Do nothing if clicking ref's element or descendent elements
if (!ref.current || ref.current.contains(event.target)) {
return;
}
handler(event);
};
document.addEventListener("mousedown", listener);
document.addEventListener("touchstart", listener);
return () => {
document.removeEventListener("mousedown", listener);
document.removeEventListener("touchstart", listener);
};
},
// Add ref and handler to effect dependencies
// It's worth noting that because passed in handler is a new ...
// ... function on every render that will cause this effect ...
// ... callback/cleanup to run every render. It's not a big deal ...
// ... but to optimize you can wrap handler in useCallback before ...
// ... passing it into this hook.
[ref, handler]
);
}
I want to get a ref, more specifically a getBoundingClientRect() on the <Header/> and <Testimonials/> component. I then want to watch for a scroll event and check if the two components ever overlap. Currently, my overlap variable never flips to true even if what appears on the page is that the two components are overlaping.
const [isIntersecting, setIsIntersecting] = useState(false)
const header = useRef(null)
const testimonials = useRef(null)
const scrollHandler = _ => {
let headerRect = header.current.getBoundingClientRect();
let testiRect = testimonials.current.getBoundingClientRect();
let overlap = !(headerRect.right < testiRect.left ||
headerRect.left > testiRect.right ||
headerRect.bottom < testiRect.top ||
headerRect.top > testiRect.bottom)
console.log(overlap) // never flips to true
};
useEffect(() => {
window.addEventListener("scroll", scrollHandler, true);
return () => {
window.removeEventListener("scroll", scrollHandler, true);
};
}, []);
const App = () => {
return (
<div className="App">
<Header />
<LandingPage />
<div style={{ height: '100vh', backgroundColor: 'black', color: 'white' }}>
</div>
<AboutPage />
<TestimonialsPage />
<Footer />
</div>
);
}
First: Components can't receive directly a ref prop, unless you are wrapping the Component itself in a React.forwardRef wrapper:
const Component = React.forwardRef((props, ref) => (
<button ref={ref}>
{props.children}
</button>
));
// Inside your Parent Component:
const ref = useRef();
<Component ref={ref}>Click me!</Component>;
Second: you can also pass a ref down to a child as a standard prop, but you can't call that prop ref since that's a special reserved word just like the key prop:
const Component= (props) => (
<button ref={props.myRef}>
{props.children}
</button>
);
// Inside your Parent Component
const ref = useRef();
<Component myRef={ref}>Click me!</Component>;
This works perfectly fine, and if it's a your personal project you
might work like this with no issues, the only downside is that you
have to use custom prop name for those refs, so the code gets harder to
read and to mantain, especially if it's a shared repo.
Third: Now that you learnt how to gain access to the DOM node of a child Component from its parent, you must know that even if usually it's safe to perform manipulations on those nodes inside a useEffect ( or a componentDidMount ) since they are executed once the DOM has rendered, to be 100% sure you will have access to the right DOM node it's always better using a callback as a ref like this:
const handleRef = (node) => {
if (node) //do something with node
};
<Component ref={handleRef}/>
Basically your function hanldeRef will be called by React during
DOM node render by passing the node itself as its first parameter,
this way you can perform a safe check on the node, and be sure it's
100% valorized when you are going to perform your DOM manipulation.
Concerning your specific question about how to access the getBoundingClientRect of a child Component DOM node, I made a working example with both the approaches:
https://stackblitz.com/edit/react-pqujuz
You'll need to define each of your components as Forwarding Refs, eg
const Header = forwardRef<HTMLElement>((_, ref) => (
<header ref={ref}>
<h1>I am the header</h1>
</header>
));
You can then pass a HTMLElement ref to your components to refer to later
const headerRef = useRef<HTMLElement>(null);
const scrollHandler = () => {
console.log("header position", headerRef.current?.getBoundingClientRect());
};
useEffect(() => {
window.addEventListener("scroll", scrollHandler);
return () => {
window.removeEventListener("scroll", scrollHandler);
};
}, []);
return (
<Header ref={headerRef} />
);
I'm using TypeScript examples since it's easier to translate back down to JS than it is to go up to TS
I am working on an onPress event in React Native. I have added two different actions onPress, onLongPress. I have two different functions associated with each of them. Now i have also added delayLongPress={250} and it's working with onLongPress. But when i try to call the onLongPress, it calls the onPress too. I don't want that to happen. I want to call the onpress when it's pressed just once and onLongPress when it's pressed for 250ms at least. How can i seperate those function calls.
Here's what i have right now:
const onLongPress = () =>{
console.log('Pressed long')
}
const onChange = () =>{
console.log('Pressed')
}
return(
<Container
onPress={onChange}
onLongPress={onLongPress}
delayLongPress={250}
>
</Container>
)
Try to wrap it with the TouchableHighlight.
export default function App() {
const onLongPress = () => {
console.log('Pressed long');
};
const onChange = () => {
console.log('Pressed');
};
return (
<TouchableHighlight
onPress={onChange}
onLongPress={onLongPress}
delayLongPress={250}
>
<Text>Press</Text> // Your child components goes here
</TouchableHighlight>
);
}
See the snack here
I want to add an eventListener to a node in my React component. I am selecting the node with a useRef hook. I am useCallback since the useRef is null on it's first render.
const offeringsContainer = useRef(null);
const setOfferingsContainer = useCallback((node) => {
if (node !== null) {
offeringsContainer.current = node;
}
}, []);
My issue is that my useEffect is not reacting to any changes done to the offeringContainer ref. Meaning, offeringContainer.current is null.
useEffect(() => {
checkHeaderState();
offeringsContainer.current.addEventListener("wheel", onOfferingsContainerWheel, { passive: false });
}, [offeringsContainer.current]);
This is my JSX:
return (
<Fragment>
<div className="card-business-products-services-title-text">
Products & Services
</div>
<div
className="card-business-products-services-container"
id="card-business-products-services-container"
onWheel={onOfferingsContainerWheel}
ref={setOfferingsContainer}
>
{renderOfferings()}
</div>
</Fragment>
);
I know I am doing something incorrectly, but my useEffect hook should be listening to any changes from offeringsContainer.current.
You can just past offeringsContainer to the ref of the component. useEffect will be invoked only when there is first rendering that's why your offeringsContainer.current will not be null.
And you forgot to remove listener after the component will be unmounted.
Your code should be like this;
const offeringsContainer = useRef(null);
useEffect(() => {
checkHeaderState();
offeringsContainer.current.addEventListener(
"wheel",
onOfferingsContainerWheel,
{ passive: false }
);
return () => offeringsContainer.current.removeEventListener("wheel");
}, []);
return (
<Fragment>
<div className="card-business-products-services-title-text">
Products & Services
</div>
<div
className="card-business-products-services-container"
id="card-business-products-services-container"
onWheel={onOfferingsContainerWheel}
ref={offeringsContainer}
>
{renderOfferings()}
</div>
</Fragment>
);
Example: https://codesandbox.io/s/wizardly-banzai-3fhju?file=/src/App.js