I am adding cursor animations to a React/Typescript project and in researching came across a CodePen (Animated Cursor React Component) that works perfectly well.
However, when converting to a Typescript file I come across the error Property 'current' does not exist on type '[boolean, Dispatch<SetStateAction<boolean>>]'.ts(2339)
on cursorVisible.current
in
const onMouseEnter = () => {
cursorVisible.current = true;
toggleCursorVisibility();
};
Property cursorVisible
is from const cursorVisible = useState(false);
What does Typescript need me to do so current
works in Typescript? Reading the React Hooks docs, I could not see reference to current
on useState
and interestingly this works as a js
file, only not in ts
.
In the past I have used current
with ref
but never across useState
hook.
Full file is
import React, { useEffect, useRef, useState } from 'react';
import MobileDetect from './MobileDetect';
interface CursorProps {
color: string;
outlineAlpha: number;
dotSize: number;
outlineSize: number;
outlineScale: number;
dotScale: number;
}
function AnimatedCursor({
color = '220, 90, 90',
outlineAlpha = 0.3,
dotSize = 8,
outlineSize = 8,
outlineScale = 5,
dotScale = 0.7,
}: CursorProps) {
// Bail if Mobile
if (typeof navigator !== 'undefined' && MobileDetect!.anyMobile())
return <></>;
const cursorOutline = useRef();
const cursorDot = useRef();
const requestRef = useRef();
const previousTimeRef = useRef();
const [width, setWidth] = useState(window.innerWidth);
const [height, setHeight] = useState(window.innerHeight);
const [mousePosition, setMousePosition] = useState({ x: 0, y: 0 });
const cursorVisible = useState(false);
const cursorEnlarged = useState(false);
const styles = {
cursors: {
zIndex: 999,
pointerEvents: 'none',
position: 'absolute',
top: '50%',
left: '50%',
borderRadius: '50%',
opacity: 0,
transform: 'translate(-50%, -50%)',
transition: 'opacity 0.15s ease-in-out, transform 0.15s ease-in-out',
},
cursorDot: {
width: dotSize,
height: dotSize,
backgroundColor: `rgba(${color}, 1)`,
},
cursorOutline: {
width: outlineSize,
height: outlineSize,
backgroundColor: `rgba(${color}, ${outlineAlpha})`,
},
};
// Hide default cursor
document.body.style.cursor = 'none';
// Mouse Events
const onMouseMove = (event: { pageX: number; pageY: number }) => {
const { pageX: x, pageY: y } = event;
setMousePosition({ x, y });
positionDot(event);
};
const onMouseEnter = () => {
cursorVisible.current = true;
toggleCursorVisibility();
};
const onMouseLeave = () => {
cursorVisible.current = false;
toggleCursorVisibility();
};
const onMouseDown = () => {
cursorEnlarged.current = true;
toggleCursorSize();
};
const onMouseUp = () => {
cursorEnlarged.current = false;
toggleCursorSize();
};
// Set window hxw
const onResize = () => {
setWidth(window.innerWidth);
setHeight(window.innerHeight);
};
/**
* Hooks
*/
useEffect(() => {
// Bail if mobile
document.addEventListener('mousemove', onMouseMove);
document.addEventListener('mouseenter', onMouseEnter);
document.addEventListener('mouseleave', onMouseLeave);
document.addEventListener('mousedown', onMouseDown);
document.addEventListener('mouseup', onMouseUp);
window.addEventListener('resize', onResize);
requestRef.current = requestAnimationFrame(animateDotOutline);
handleLinkEvents();
return () => {
document.removeEventListener('mousemove', onMouseMove);
document.removeEventListener('mouseenter', onMouseEnter);
document.removeEventListener('mouseleave', onMouseLeave);
document.removeEventListener('mousedown', onMouseDown);
document.removeEventListener('mouseup', onMouseUp);
window.removeEventListener('resize', onResize);
cancelAnimationFrame(requestRef.current);
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
let { x, y } = mousePosition;
const winDimensions = { width, height };
let endX = winDimensions.width / 2;
let endY = winDimensions.height / 2;
/**
* Toggle Cursor Visiblity
*/
function toggleCursorVisibility() {
if (cursorVisible.current) {
cursorDot.current.style.opacity = 1;
cursorOutline.current.style.opacity = 1;
} else {
cursorDot.current.style.opacity = 0;
cursorOutline.current.style.opacity = 0;
}
}
/**
* Position Dot (cursor)
* @param {event}
*/
function positionDot(e: { pageX: number; pageY: number }) {
cursorVisible.current = true;
toggleCursorVisibility();
// Position the dot
endX = e.pageX;
endY = e.pageY;
cursorDot.current.style.top = `${endY}px`;
cursorDot.current.style.left = `${endX}px`;
}
/**
* Toggle Cursors Size/Scale
*/
function toggleCursorSize() {
if (cursorEnlarged.current) {
cursorDot.current.style.transform = `translate(-50%, -50%) scale(${dotScale})`;
cursorOutline.current.style.transform = `translate(-50%, -50%) scale(${outlineScale})`;
} else {
cursorDot.current.style.transform = 'translate(-50%, -50%) scale(1)';
cursorOutline.current.style.transform = 'translate(-50%, -50%) scale(1)';
}
}
/**
* Handle Links Events
* Applies mouseover/out hooks on all links
* to trigger cursor animation
*/
function handleLinkEvents() {
document.querySelectorAll('a').forEach((el) => {
el.addEventListener('mouseover', () => {
cursorEnlarged.current = true;
toggleCursorSize();
});
el.addEventListener('mouseout', () => {
cursorEnlarged.current = false;
toggleCursorSize();
});
});
}
/**
* Animate Dot Outline
* Aniamtes cursor outline with trailing effect.
* @param {number} time
*/
const animateDotOutline = (time: undefined) => {
if (previousTimeRef.current !== undefined) {
x += (endX - x) / 8;
y += (endY - y) / 8;
cursorOutline.current.style.top = `${y}px`;
cursorOutline.current.style.left = `${x}px`;
}
previousTimeRef.current = time;
requestRef.current = requestAnimationFrame(animateDotOutline);
};
return (
<>
<div
ref={cursorOutline}
id="cursor-outline"
style={{ ...styles.cursors, ...styles.cursorOutline }}
/>
<div
ref={cursorDot}
id="cursor-inner"
style={{ ...styles.cursors, ...styles.cursorDot }}
/>
</>
);
}
export default AnimatedCursor;
You'll have to ask the author of the code you're using. useState
returns an array with the current value and a setter function to change the value. Normally you would use it like this:
let [cursorVisible, setCursorVisible] = useState(false);
// instead of cursorVisible.current = true
setCursorVisible(true);
There's no 'current' property on the array, unless maybe it is set by other code which would be bad form I think.