I'm working on this Tooltip where if you mouse over it it'll show a tooltip:
But if you tap on it (with your finger) it'll show full screen (for mobile support):
Code looks like this:
export default function Tooltip({ message, children }: Props) {
const [showSmallTip, setShowSmallTip] = useState(false);
const [showBigTap, setShowBigTip] = useState(false);
const ref = useRef(null);
const pos = useBoundingBox(ref);
const handleMouseEnter = useCallback(() => {
setShowSmallTip(true);
}, [setShowSmallTip]);
const handleMouseLeave = useCallback(() => {
setShowSmallTip(false);
}, [setShowSmallTip]);
const handleTap = useCallback(() => {
console.log("TAP!")
setShowBigTip(true);
setShowSmallTip(false);
}, [setShowBigTip]);
const closeFullscreen = useCallback((ev:MouseEvent<HTMLElement>) => {
console.log('CLOSE!!!')
ev.stopPropagation();
setShowBigTip(false);
setShowSmallTip(false);
}, [setShowBigTip]);
const onTap = useTap(handleTap);
return <>
<Wrapper ref={ref} onMouseEnter={handleMouseEnter} onMouseLeave={handleMouseLeave} {...onTap}>
{children}
</Wrapper>
{showSmallTip ? <End container={SCROLL_ROOT}><StyledTooltip style={{ top: pos.bottom, left: (pos.left + pos.right) / 2 }}>{message}</StyledTooltip></End> : null}
{showBigTap ? <End><FullscreenTip onClick={closeFullscreen}><FullscreenText>{message}</FullscreenText></FullscreenTip></End>:null}
</>
}
Where useTap is:
export default function useTap<T = Element>(callback: VoidCallback, options?: Options): TouchEvents<T> {
const data = useRef<TouchData>(Object.create(null));
return useMemo<TouchEvents<T>>(() => {
const opt = { ...DEFAULT_OPTIONS, ...options } as Required<Options>;
return {
onTouchStart(ev) {
data.current = {
time: ev.timeStamp,
x: ev.changedTouches[0].screenX,
y: ev.changedTouches[0].screenY,
}
},
onTouchEnd(ev) {
const mx = ev.changedTouches[0].screenX - data.current.x;
const my = ev.changedTouches[0].screenY - data.current.y;
const moved = Math.sqrt(mx**2 + my**2);
const elapsed = ev.timeStamp - data.current.time;
if (moved < opt.moveThreshold && elapsed < opt.pressDelay) {
// setTimeout(() => {
callback();
// }, 0);
}
}
}
}, [callback, options])
}
The issue I'm having is when you tap on the icon it opens and closes the fullscreen tooltip immediately. i.e., it prints
TAP!
CLOSE!!!
with one single tap.
Now I know touchend fires before click, but what I can't figure out is why that would even matter?? If you look at my placement of the {...onTap} and onClick={closeFullscreen} handlers, they're siblings. The events shouldn't bubble that way (neither in the native DOM nor React's VDOM), and I certainly didn't click on <FullscreenTip> so how on earth is closeFullscreen firing?
<End> is a portal.
DEMO
Try to call ev.preventDefault() on onTouchEnd.
According to spec the touchend event is "cancelable" which means that you can use .preventDefault() to prevent mouse events.
If the preventDefault method is called on this event, it should prevent any default actions caused by any touch events associated with the same active touch point, including mouse events or scrolling.
Related
I want to create a generic react hook that will add a scroll event to the element and return a boolean indicating that the user has scrolled to the top of the element.
Now, the problem is this element might not be visible right away. Hence I'm not able to use useEffect. As I understand in that situation it is advised to use useCallback
So I did, and it works:
function useHasScrolled() {
const [hasScrolled, setHasScrolled] = useState(false);
const ref = useRef(null);
const setRef = useCallback((element) => {
const handleScroll = (e) => {
setHasScrolled(e.target.scrollTop !== 0);
};
if (element) {
element.addEventListener("scroll", handleScroll);
}
ref.current = element;
}, []);
return {
hasScrolled,
scrollingElementRef: setRef
};
}
I can use my hook like this:
const { hasScrolled, scrollingElementRef } = useHasScrolled();
....
return <div ref={scrollingElementRef}>....
However, the problem is, I don't know how to remove the event listener. With the useEffect hook, it's pretty straightforward - you just return the cleanup function.
Here's the codesandbox, if you want to check the implementation: https://codesandbox.io/s/pedantic-dhawan-83fdw3
Expected behavior - when node is removed from DOM - event listeners will be also removed and collected by GC.
But
Codesandbox example is a bit tricky, React treats
<div>Loading...</div>
and
<div className="scrollingDiv" ref={scrollingElementRef}>
<h1>Hello, I've finally loaded!</h1>
<Lorem />
</div>
as a same div, same object, just with different props (className and children), so when div.scrollingDiv is replaced by conditional rendering to div(loading) - event listeners are still there and accumulating.
This behavior can be fixed as is by using keys.
{loading ? (
<div key="div1">Loading...</div>
) : (
<div key="div2" className="scrollingDiv" ref={scrollingElementRef}>
<h1>Hello, I've finally loaded!</h1>
<Lorem />
</div>
)}
In that way event listeners will be removed as expected.
Another solution is to add 1 more useRef and useEffect to the custom hook to store and execute actual unsubscribe function:
function useHasScrolled() {
const [hasScrolled, setHasScrolled] = useState(false);
const ref = useRef(null);
const unsubscribeRef = useRef(null);
const setRef = useCallback((element) => {
const eventName = "scroll";
const handleScroll = (e) => {
setHasScrolled(e.target.scrollTop !== 0);
};
if (unsubscribeRef.current) {
unsubscribeRef.current();
unsubscribeRef.current = null;
}
if (element) {
element.addEventListener(eventName, handleScroll);
unsubscribeRef.current = () => {
console.log("removeEventListener called on: ", element);
element.removeEventListener(eventName, handleScroll);
};
ref.current = element;
} else {
unsubscribeRef.current = null;
ref.current = null;
}
}, []);
useEffect(() => {
return () => {
if (unsubscribeRef.current) {
unsubscribeRef.current();
unsubscribeRef.current = null;
}
};
}, []);
return {
hasScrolled,
scrollingElementRef: setRef
};
}
That code will work without adding key.
Utility code for Chrome dev console to count scroll listeners:
Array.from(document.querySelectorAll('*'))
.reduce(function(pre, dom){
var clks = getEventListeners(dom).scroll;
pre += clks ? clks.length || 0 : 0;
return pre
}, 0)
Updated codesandbox: https://codesandbox.io/s/angry-einstein-6fb1u4?file=/src/App.js
I'm trying to 'move' in my 10x10 grid by updating the activeCellId state. However none of the methods I tried works. This is my code.
const GridCells: React.FC = () => {
const gridArray = [...Array(100).keys()];
const color = [
"bg-slate-50",
"bg-slate-100",
"bg-slate-200",
"bg-slate-300",
"bg-slate-400",
"bg-slate-500",
"bg-slate-600",
"bg-slate-700",
"bg-slate-800",
"bg-slate-900",
];
const [activeCellId, setActiveCellId] = useState(42);
// useEffect(() => {
// document.addEventListener("keydown", updateActiveCellId, false);
// }, []); // this doesn't work. the activeCellId is only incremented once, and afterwards the setActiveCellId doesn't get called at all
const updateActiveCellId = (e: React.KeyboardEvent) => {
// will eventually be a switch case logic here, for handling arrow up, left, right down
console.log(activeCellId);
setActiveCellId(activeCellId + 1);
};
return (
<div
className="grid-rows-10 grid grid-cols-10 gap-0.5"
// onKeyDown={updateActiveCellId} this also doesn't work
>
{gridArray.map((value, id) => {
const colorId = Math.floor(id / 10);
return (
<div
key={id}
className={
"h-10 w-10 "
+ color[colorId]
+ (id === activeCellId ? " scale-125 bg-yellow-400" : "")
}
>
{id}
</div>
);
})}
</div>
);
};
I'm trying to update a state in the react component by pressing certain keys. I've tried UseEffect with [] dep array and tried onKeyDown and it also doesn't work. I also tried following this useRef way it doesn't work too.
const innerRef = useRef(null);
useEffect(() => {
const div = innerRef.current;
div.addEventListener("keydown", updateActiveCellId, false);
}, []); // this doesn't work at all
const updateActiveCellId = (e: React.KeyboardEvent) => {
console.log(activeCellId);
setActiveCellId(activeCellId + 1);
};
return (
<div
className="grid-rows-10 grid grid-cols-10 gap-0.5"
ref={innerRef}
>
...
)
Try this:
useEffect(() => {
document.addEventListener("keydown", updateActiveCellId, false);
return () => {
document.removeEventListener("keydown", updateActiveCellId, false);
}
}, [activeCellId]);
The [activeCellId] is the dependency of useEffect. Everytimes activeCellId changes, the function inside useEffect will run.
You had an empty dependency, so it ran on initial component mount only.
The returned function containing removeEventListner is executed when the component unmounts (See cleanup function in the docs). That is to ensure you have only one event listener runnign at once.
Documentation
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 🙌
I have a slider component, which should stop moving after mouse is up. I have went through the forum and my code is very similar to the one here
const Slider = ({ mainColour }) => {
const [cursorPos, setCursorPos] = React.useState(0);
const [isSliding, setSliding] = React.useState(false);
const ref = React.useRef();
const drag = e => {
console.log("dragging");
setCursorPos(e.pageY);
};
const startDrag = e => {
setSliding(true);
window.addEventListener("mousemove", drag);
window.addEventListener("mouseup", function() {
ref.current.onmousedown = null;
ref.current.onmouseup = null;
ref.current.onmousemove = null;
setSliding(false);
window.onmouseup = null;
});
};
return (
<div
className={`cursor ${isSliding ? "active" : ""}`}
ref={ref}
style={{
top: `${cursorPos}px`,
backgroundColor: `${mainColour}`
}}
onMouseDown={event => startDrag(event)}
></div>
);
};
export default Slider;
However, when startDrag triggers, the window.onmouseup listener doesn't seem to be working and does not stop the slider. Will be appreciated for any insights why it doesn't work.
https://codesandbox.io/s/lucid-sunset-8e78r
React can trigger mouseup, you just need to use window.removeEventListener for drag() when you mouseup. That's why you see dragging in the console after mouseup, you just forgot to unsubscribe from the event :)
window.onmouseup = null; is not the same as window.removeEventListener("mousemove").
const Slider = ({ mainColour }) => {
const [cursorPos, setCursorPos] = React.useState(0);
const [isSliding, setSliding] = React.useState(false);
const ref = React.useRef();
const drag = e => {
console.log("dragging");
setCursorPos(e.pageY);
};
useEffect(() => {
if (isSliding) {
window.addEventListener("mousemove", drag);
}
}, [isSliding]);
useEffect(() => {
window.addEventListener("mouseup", function() {
window.removeEventListener("mousemove", drag);
setSliding(false);
});
});
const startDrag = () => setSliding(true);
return (
<div
className={`cursor ${isSliding ? "active" : ""}`}
ref={ref}
style={{
top: `${cursorPos}px`,
backgroundColor: `${mainColour}`
}}
onMouseDown={event => startDrag(event)}
/>
);
};
I agree with the comment from artanik, but with a very slight change. Instead of using the useEffect without any dependencies, and constantly adding and removing event listeners from the window object, I would rather only set and unset it when the isSliding changes value. Also it seems that the ref is not used anywhere, so I presume instead of using the window object you could set it only for the element in the ref.
The purpose of triggering the useEffect with an empty array once is to not run it every render. Imagine a component that would have a lot of state changing and data going through it, adding and removing a bunch of event listeners in one go in every render is not needed.
const Slider = ({ mainColour }) => {
const [cursorPos, setCursorPos] = React.useState(0);
const [isSliding, setSliding] = React.useState(false);
///only do this once, when the component mounts
useEffect(() => {
window.addEventListener("mousedown", startDrag);
window.addEventListener("mouseup", endDrag);
},[]);
//and setting and unseting the event as needed
useEffect(() => {
if (isSliding) {
window.onmousemove = handleDrag;
}
else{
window.onmousemove = null;
}
}, [isSliding]);
const startDrag = () => setSliding(true);
const endDrag = () => setSliding(false);
const handleDrag = (e) =>{
console.log("dragging");
setCursorPos(e.pageY);
}
return (
<div
className={`cursor ${isSliding ? "active" : ""}`}
style={{
top: `${cursorPos}px`,
backgroundColor: `${mainColour}`
}}
/>
);
};
I am building a Minesweeper game with React and want to perform a different action when a cell is single or double clicked. Currently, the onDoubleClick function will never fire, the alert from onClick is shown. If I remove the onClick handler, onDoubleClick works. Why don't both events work? Is it possible to have both events on an element?
/** #jsx React.DOM */
var Mine = React.createClass({
render: function(){
return (
<div className="mineBox" id={this.props.id} onDoubleClick={this.props.onDoubleClick} onClick={this.props.onClick}></div>
)
}
});
var MineRow = React.createClass({
render: function(){
var width = this.props.width,
row = [];
for (var i = 0; i < width; i++){
row.push(<Mine id={String(this.props.row + i)} boxClass={this.props.boxClass} onDoubleClick={this.props.onDoubleClick} onClick={this.props.onClick}/>)
}
return (
<div>{row}</div>
)
}
})
var MineSweeper = React.createClass({
handleDoubleClick: function(){
alert('Double Clicked');
},
handleClick: function(){
alert('Single Clicked');
},
render: function(){
var height = this.props.height,
table = [];
for (var i = 0; i < height; i++){
table.push(<MineRow width={this.props.width} row={String.fromCharCode(97 + i)} onDoubleClick={this.handleDoubleClick} onClick={this.handleClick}/>)
}
return (
<div>{table}</div>
)
}
})
var bombs = ['a0', 'b1', 'c2'];
React.renderComponent(<MineSweeper height={5} width={5} bombs={bombs}/>, document.getElementById('content'));
This is not a limitation of React, it is a limitation of the DOM's click and dblclick events. As suggested by Quirksmode's click documentation:
Don't register click and dblclick events on the same element: it's impossible to distinguish single-click events from click events that lead to a dblclick event.
For more current documentation, the W3C spec on the dblclick event states:
A user agent must dispatch this event when the primary button of a pointing device is clicked twice over an element.
A double click event necessarily happens after two click events.
Edit:
One more suggested read is jQuery's dblclick handler:
It is inadvisable to bind handlers to both the click and dblclick events for the same element. The sequence of events triggered varies from browser to browser, with some receiving two click events before the dblclick and others only one. Double-click sensitivity (maximum time between clicks that is detected as a double click) can vary by operating system and browser, and is often user-configurable.
Instead of using ondoubleclick, you can use event.detail to get the current click count. It's the number of time the mouse's been clicked in the same area in a short time.
const handleClick = (e) => {
switch (e.detail) {
case 1:
console.log("click");
break;
case 2:
console.log("double click");
break;
case 3:
console.log("triple click");
break;
}
};
return <button onClick={handleClick}>Click me</button>;
In the example above, if you triple click the button it will print all 3 cases:
click
double click
triple click
Live Demo
The required result can be achieved by providing a very slight delay on firing off the normal click action, which will be cancelled when the double click event will happen.
let timer = 0;
let delay = 200;
let prevent = false;
doClickAction() {
console.log(' click');
}
doDoubleClickAction() {
console.log('Double Click')
}
handleClick() {
let me = this;
timer = setTimeout(function() {
if (!prevent) {
me.doClickAction();
}
prevent = false;
}, delay);
}
handleDoubleClick(){
clearTimeout(timer);
prevent = true;
this.doDoubleClickAction();
}
< button onClick={this.handleClick.bind(this)}
onDoubleClick = {this.handleDoubleClick.bind(this)} > click me </button>
You can use a custom hook to handle simple click and double click like this :
import { useState, useEffect } from 'react';
function useSingleAndDoubleClick(actionSimpleClick, actionDoubleClick, delay = 250) {
const [click, setClick] = useState(0);
useEffect(() => {
const timer = setTimeout(() => {
// simple click
if (click === 1) actionSimpleClick();
setClick(0);
}, delay);
// the duration between this click and the previous one
// is less than the value of delay = double-click
if (click === 2) actionDoubleClick();
return () => clearTimeout(timer);
}, [click]);
return () => setClick(prev => prev + 1);
}
then in your component you can use :
const click = useSingleAndDoubleClick(callbackClick, callbackDoubleClick);
<button onClick={click}>clic</button>
Edit:
I've found that this is not an issue with React 0.15.3.
Original:
For React 0.13.3, here are two solutions.
1. ref callback
Note, even in the case of double-click, the single-click handler will be called twice (once for each click).
const ListItem = React.createClass({
handleClick() {
console.log('single click');
},
handleDoubleClick() {
console.log('double click');
},
refCallback(item) {
if (item) {
item.getDOMNode().ondblclick = this.handleDoubleClick;
}
},
render() {
return (
<div onClick={this.handleClick}
ref={this.refCallback}>
</div>
);
}
});
module.exports = ListItem;
2. lodash debounce
I had another solution that used lodash, but I abandoned it because of the complexity. The benefit of this was that "click" was only called once, and not at all in the case of "double-click".
import _ from 'lodash'
const ListItem = React.createClass({
handleClick(e) {
if (!this._delayedClick) {
this._delayedClick = _.debounce(this.doClick, 500);
}
if (this.clickedOnce) {
this._delayedClick.cancel();
this.clickedOnce = false;
console.log('double click');
} else {
this._delayedClick(e);
this.clickedOnce = true;
}
},
doClick(e) {
this.clickedOnce = undefined;
console.log('single click');
},
render() {
return (
<div onClick={this.handleClick}>
</div>
);
}
});
module.exports = ListItem;
on the soapbox
I appreciate the idea that double-click isn't something easily detected, but for better or worse it IS a paradigm that exists and one that users understand because of its prevalence in operating systems. Furthermore, it's a paradigm that modern browsers still support. Until such time that it is removed from the DOM specifications, my opinion is that React should support a functioning onDoubleClick prop alongside onClick. It's unfortunate that it seems they do not.
Here's what I have done. Any suggestions for improvement are welcome.
class DoubleClick extends React.Component {
state = {counter: 0}
handleClick = () => {
this.setState(state => ({
counter: this.state.counter + 1,
}))
}
handleDoubleClick = () => {
this.setState(state => ({
counter: this.state.counter - 2,
}))
}
render() {
return(
<>
<button onClick={this.handleClick} onDoubleClick={this.handleDoubleClick>
{this.state.counter}
</button>
</>
)
}
}
Typescript React hook to capture both single and double clicks, inspired by #erminea-nea 's answer:
import {useEffect, useState} from "react";
export function useSingleAndDoubleClick(
handleSingleClick: () => void,
handleDoubleClick: () => void,
delay = 250
) {
const [click, setClick] = useState(0);
useEffect(() => {
const timer = setTimeout(() => {
if (click === 1) {
handleSingleClick();
}
setClick(0);
}, delay);
if (click === 2) {
handleDoubleClick();
}
return () => clearTimeout(timer);
}, [click, handleSingleClick, handleDoubleClick, delay]);
return () => setClick(prev => prev + 1);
}
Usage:
<span onClick={useSingleAndDoubleClick(
() => console.log('single click'),
() => console.log('double click')
)}>click</span>
This is the solution of a like button with increment and discernment values based on solution of Erminea.
useEffect(() => {
let singleClickTimer;
if (clicks === 1) {
singleClickTimer = setTimeout(
() => {
handleClick();
setClicks(0);
}, 250);
} else if (clicks === 2) {
handleDoubleClick();
setClicks(0);
}
return () => clearTimeout(singleClickTimer);
}, [clicks]);
const handleClick = () => {
console.log('single click');
total = totalClicks + 1;
setTotalClicks(total);
}
const handleDoubleClick = () => {
console.log('double click');
if (total > 0) {
total = totalClicks - 1;
}
setTotalClicks(total);
}
return (
<div
className="likeButton"
onClick={() => setClicks(clicks + 1)}
>
Likes | {totalClicks}
</div>
)
Here is one way to achieve the same with promises. waitForDoubleClick returns a Promise which will resolve only if double click was not executed. Otherwise it will reject. Time can be adjusted.
async waitForDoubleClick() {
return new Promise((resolve, reject) => {
const timeout = setTimeout(() => {
if (!this.state.prevent) {
resolve(true);
} else {
reject(false);
}
}, 250);
this.setState({ ...this.state, timeout, prevent: false })
});
}
clearWaitForDoubleClick() {
clearTimeout(this.state.timeout);
this.setState({
prevent: true
});
}
async onMouseUp() {
try {
const wait = await this.waitForDoubleClick();
// Code for sinlge click goes here.
} catch (error) {
// Single click was prevented.
console.log(error)
}
}
Here's my solution for React in TypeScript:
import { debounce } from 'lodash';
const useManyClickHandlers = (...handlers: Array<(e: React.UIEvent<HTMLElement>) => void>) => {
const callEventHandler = (e: React.UIEvent<HTMLElement>) => {
if (e.detail <= 0) return;
const handler = handlers[e.detail - 1];
if (handler) {
handler(e);
}
};
const debounceHandler = debounce(function(e: React.UIEvent<HTMLElement>) {
callEventHandler(e);
}, 250);
return (e: React.UIEvent<HTMLElement>) => {
e.persist();
debounceHandler(e);
};
};
And an example use of this util:
const singleClickHandler = (e: React.UIEvent<HTMLElement>) => {
console.log('single click');
};
const doubleClickHandler = (e: React.UIEvent<HTMLElement>) => {
console.log('double click');
};
const clickHandler = useManyClickHandlers(singleClickHandler, doubleClickHandler);
// ...
<div onClick={clickHandler}>Click me!</div>
I've updated Erminea Nea solution with passing an original event so that you can stop propagation + in my case I needed to pass dynamic props to my 1-2 click handler. All credit goes to Erminea Nea.
Here is a hook I've come up with:
import { useState, useEffect } from 'react';
const initialState = {
click: 0,
props: undefined
}
function useSingleAndDoubleClick(actionSimpleClick, actionDoubleClick, delay = 250) {
const [state, setState] = useState(initialState);
useEffect(() => {
const timer = setTimeout(() => {
// simple click
if (state.click === 1) actionSimpleClick(state.props);
setState(initialState);
}, delay);
// the duration between this click and the previous one
// is less than the value of delay = double-click
if (state.click === 2) actionDoubleClick(state.props);
return () => clearTimeout(timer);
}, [state.click]);
return (e, props) => {
e.stopPropagation()
setState(prev => ({
click: prev.click + 1,
props
}))
}
}
export default useSingleAndDoubleClick
Usage in some component:
const onClick = useSingleAndDoubleClick(callbackClick, callbackDoubleClick)
<button onClick={onClick}>Click me</button>
or
<button onClick={e => onClick(e, someOtherProps)}>Click me</button>
import React, { useState } from "react";
const List = () => {
const [cv, uv] = useState("nice");
const ty = () => {
uv("bad");
};
return (
<>
<h1>{cv}</h1>
<button onDoubleClick={ty}>Click to change</button>
</>
);
};
export default List;