Reputation: 457
EDIT: I've setup a codesandbox: https://codesandbox.io/s/magical-poitras-yogur?file=/src/App.tsx
I'm trying to make a custom hook to div element in react to add event listeners.
I found this 'general' solution:
function useMyCustomHook<T extends HTMLElement>{
const myRef = useRef<T>(null)
// do something with the ref, e.g. adding event listeners
return {ref: myRef}
}
function MyComponent(){
const {ref: myElementRef} = useMyCustomHook<HTMLDivElement>()
return <div ref={myElementRef}>A Div</div>
}
from: Cannot assign RefObject<HTMLDivElement> to RefObject<HTMLElement> instance
Which I've tried to implement in my code below. I've been playing around for hours and finally got it down to only one error, but I don't know how to solve it. The error is in my useHover
function expression at the first =
. error is: '(' expected.ts(1005)
my code now:
const Hooks = (props: any) => {
const [hoverRef, hovered] = useHover();
const style = {
backgroundColor: hovered ? "red" : "",
};
return (
<div ref={hoverRef} style={style}>
<h1>Hooks!</h1>
</div>
);
};
const useHover:<HTMLDivElement extends HTMLElement> = () => {
// ERROR HERE ^ the first equal sign. '(' expected.ts(1005)
const [value, setValue] = useState(false);
const ref = useRef<HTMLDivElement>(null);
const handleMouseOver = () => setValue(true);
useEffect(() => {
const node = ref.current;
if (node) {
node.addEventListener("mouseover", handleMouseOver);
return () => {
node.removeEventListener("mouseover", handleMouseOver);
}
}
}, []);
return [ref, value];
};
Any help is appreciated!
Upvotes: 0
Views: 2812
Reputation: 61
You have placed the generic in the wrong place
const someFunction = <Generic>(arg: Generic) => {
// Implement function
}
In your case should be
const useHover = <HTMLDivElement extends HTMLElement>() => {
const [value, setValue] = useState(false);
const ref = useRef<HTMLDivElement>(null);
const handleMouseOver = () => setValue(true);
useEffect(() => {
const node = ref.current;
if (node) {
node.addEventListener("mouseover", handleMouseOver);
return () => {
node.removeEventListener("mouseover", handleMouseOver);
}
}
}, []);
return [ref, value];
};
Upvotes: 1
Reputation: 33748
Try this: Instead of using a ref
and imperatively manipulating native DOM events, create element attributes for the events you want to handle, and return those for use with your React element:
Note: You can ignore the CSS and the first four
<script>
elements (they're just there so that the TypeScript React syntax will work in the snippet demo).
body {
font-family: sans-serif;
}
.target {
border: 1px solid;
padding: 1rem;
}
<script src="https://unpkg.com/[email protected]/umd/react.development.js"></script>
<script src="https://unpkg.com/[email protected]/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/@babel/[email protected]/babel.min.js"></script>
<script>Babel.registerPreset('tsx', {presets: [[Babel.availablePresets['typescript'], {allExtensions: true, isTSX: true}]]});</script>
<div id="root"></div>
<script type="text/babel" data-type="module" data-presets="tsx,react">
/**
* The following line is here because this Stack Overflow snippet uses the
* UMD module for React. In your code, you'd use the commented `import` lines
* below it.
*/
const {useMemo, useState} = React;
// import {useMemo, useState} from 'react';
// import type {DetailedHTMLProps, HTMLAttributes, ReactElement} from 'react';
type HoverData<T extends HTMLElement> = {
hoverProps: DetailedHTMLProps<HTMLAttributes<T>, T>;
isHovered: boolean;
};
function useHover <T extends HTMLElement>(): HoverData<T> {
const [isHovered, setIsHovered] = useState(false);
const hoverProps: HoverData<T>['hoverProps'] = useMemo(() => ({
onMouseEnter: () => setIsHovered(true),
onMouseLeave: () => setIsHovered(false),
}), [setIsHovered]);
return {hoverProps, isHovered};
}
function Example (): ReactElement {
const {hoverProps, isHovered} = useHover<HTMLDivElement>();
return (
<div>
<h1>Hover the text below</h1>
<div {...hoverProps} className="target">
{isHovered ? 'Now move it away' : 'Move pointer here'}
</div>
</div>
);
}
ReactDOM.render(<Example />, document.getElementById('root'));
</script>
Upvotes: 1