react save possibility to select text with re-render component - javascript

i have component with text. It changed component own state with mouse click. But i want to save possibility to select and copy in by long click. Is there way to make it ? Selection is reset after rerender component. Code for example:
const App = () => {
const [someState, setSomeState] = React.useState(0);
const clickHandler = () => {
setSomeState(someState + 1);
}
return (
<div
className="App"
onClick={clickHandler}
>
{'State ' + someState}
</div>
);
};
ReactDOM.render(<App />, document.getElementById("root"));

How about using onMouseDown and onMouseUp events yourself and calculate the time the user took to click instead of using onClick?
You could for example do something like this:
const App = () => {
const [someState, setSomeState] = React.useState(0);
const [timeDown, setTimeDown] = React.useState(-1);
const clickHandler = () => setSomeState(someState + 1);
const handleMouseDown = () => setTimeDown(Date.now()); // Save the time of the mousedown event
const handleMouseUp = () => {
const timeUp = Date.now();
const timeDiff = timeUp - timeDown; // Calculate the time the user took to click and hold
if (timeDiff < 1000) { // If it's shorter than 1000ms (1s) execute the normal click handler
clickHandler();
} else { // Execute some other logic, or just ignore the click
// handleLongClick();
}
};
return (
<div
className="App"
onMouseDown={handleMouseDown}
onMouseUp={handleMouseUp}
>
{"State " + someState}
</div>
);
};
You can find a quick codesandbox as a demo here

Related

How do you grab an element and dynamically add or remove styles from it in react native

I'm coming from the world of React and trying to translate a component into React Native. In react we can easily grab an element with useRef and then add or remove classes.
Is there a way to easily translate this to react native as I see when I inspect the current property of the <Text> element it looks nothing like an html element.
Here is the file I'm trying to convert:
import React, {useState, useEffect, useRef} from 'react';
// styles
import styles from './whiteTextReveal.module.css';
export const WhiteTextReveal = props => {
// props
const {text, duration, callback} = props;
// local
const leftMask = useRef(null);
const rightMask = useRef(null);
const textRef = useRef(null);
const animationTimeouts = useRef([]);
useEffect(() => {
reveal();
return () => {
animationTimeouts.current.map(val => clearTimeout(val));
}
}, [text]);
function reveal() {
let time = 0;
// reveal first white masks
const timeout1 = setTimeout(() => {
// cleanup if called successively
textRef.current.classList.remove(styles.shrink);
leftMask.current.classList.remove(styles.moveRight);
rightMask.current.classList.remove(styles.moveLeft);
leftMask.current.classList.add(styles.moveLeft);
rightMask.current.classList.add(styles.moveRight);
}, 1000*time);
animationTimeouts.current = [...animationTimeouts.current, timeout1];
// reveal text behind first white mask
time = time + .8; // come from the css file .mask.left.moveLeft
const timeout2 = setTimeout(() => {
textRef.current.classList.remove(styles.hide);
leftMask.current.classList.remove(styles.moveLeft);
rightMask.current.classList.remove(styles.moveRight);
leftMask.current.classList.add(styles.moveRight);
rightMask.current.classList.add(styles.moveLeft);
}, 1000*time);
animationTimeouts.current = [...animationTimeouts.current, timeout2];
// move mask to cover text again
time = time + .5 + duration; // come from the css file .mask.left.moveRight
const timeout3 = setTimeout(() => {
textRef.current.classList.add(styles.shrink);
const timeout4 = setTimeout(() => {
textRef.current.classList.add(styles.hide);
callback()
}, .7*1000);
animationTimeouts.current = [...animationTimeouts.current, timeout4];
}, time*1000);
animationTimeouts.current = [...animationTimeouts.current, timeout3];
}
return (
<div className={styles.container}>
<span ref={textRef} className={`${styles.text} ${styles.hide}`}>{text}</span>
<div ref={leftMask} className={`${styles.mask} ${styles.left}`}/>
<div ref={rightMask} className={`${styles.mask} ${styles.right}`}/>
</div>
)
};
This is a text animation reveal that paints a white strip over text, slides back, and the scales the text to 0 before telling the parent component it finished.
As you can see from these lines:
textRef.current.classList.remove(styles.shrink);
leftMask.current.classList.remove(styles.moveRight);
rightMask.current.classList.remove(styles.moveLeft);
I am grabbing the ref and then removing classes. And the lines that follow after add those classes back in specific ways.

having a function run every ten seconds or when an input changes

I have a simple react app where a user inputs a quantity and an api call is made to convert that value into an equivalent amount of a very unstable cryptocurrency, and that converted value is displayed on the screen.
Crucially, I want the converted value to be updated every ten seconds or when a user changes the input value.
I'm confused as to how to implement this. Any advice?
I don't know react, but in native JS it's pretty simple. Here's some code that sets your timer then clears and resets when you call an input change. Hope this is easily adaptable to react!
function cryptoTimer() {
this.value = null
this.timer = null
this.getValue = () => {
this.value = "some api response"
console.log(this.value)
}
this.startTimer = () => {
this.timer = setInterval(() => { this.getValue() }, 10000)
}
this.clearTimer = () => {
clearInterval(this.timer)
}
this.inputChange = (elem) => {
console.log('input changed, value is '+elem.value)
this.clearTimer()
this.init()
}
this.init = () => {
this.getValue()
this.startTimer()
}
this.init()
}
var timer = new cryptoTimer
<!-- some input on change action -->
<input onkeyup="timer.inputChange(this)" type="text" />
Here is an example
import React from "react";
export function App() {
// Used to store the intervalId
const timerRef = React.useRef();
// Used to store the value in the input element
const inputRef = React.useRef("");
// This is for storing the transformed value you get from API
const [transformedValue, setTransformedValue] = React.useState("");
// In this function, you can make the request to the API and update
// the transformed value. Remember to reset the interval.
const transformValue = async () => {
const input = inputRef.current;
// Invoke your api instead
const randomTransformation = `${input} ${new Date().toLocaleTimeString()}`;
setTransformedValue(randomTransformation);
resetInterval();
};
// This will just reset the interval
const resetInterval = () => {
if (timerRef.current) {
clearInterval(timerRef.current);
}
timerRef.current = setInterval(transformValue, 10 * 1000);
};
return (
<div className="container">
<label htmlFor="input">Input</label>
<input
id="input"
type="text"
value={inputRef.current}
onChange={(e) => {
inputRef.current = e.target.value;
transformValue();
}}
/>
<p>Transformed Value: {transformedValue}</p>
</div>
);
}

Understanding the useState hook behaviour

I'm currently in the process of implementing a ripple effect in my application. I'm using the Material UI Button (https://material-ui.com/components/buttons/) as a starting point for this.
During my own implementation I've run into some behaviour I'm unsure about. I've created a corresponding sandbox with a basic example, https://codesandbox.io/s/zealous-cloud-69uvh?file=/src/App.js.
The Container is the Parent Component and the Ripple is the Child Component. The Container has a onMouseUp and onMouseDown handler, which adds and removes the child component respectively.
Below is a small extract of the onMouseDown logic. The rippleCounter variable is a ref object that keeps a count which is used to set the key of the Ripple.
setRipples((oldRipples) => {
return [
...oldRipples,
<Ripple
key={rippleCounter.current}
timeout={500}
rippleX={rippleX}
rippleY={rippleY}
rippleSize={rippleSize}
/>
];
});
rippleCounter.current += 1;
};
I understand that useState is asynchronous, so there is no guarantee that the key set against the Ripple component will be before or after it is incremented. This is where I'm having some difficulties understanding.
Example 2 follows this asynchronous behaviour, as the Ripple component is having its key value set either before or after. This results in the ripple being messed up as the same key is implemented in some cases.
Example 1 doesn't appear to follow this behaviour. The setRipples function is always run in an synchronous fashion, with rippleCounter always being incremented after the setRipples function is run. i.e. Same key is never useed.
Example 1
State being managed and updated before child component is removed.
const Ripple = ({
in: inProp,
onExited = () => {},
timeout,
rippleSize,
rippleY,
rippleX
}) => {
const [leaving, setLeaving] = useState(false);
const style = {
width: rippleSize,
height: rippleSize,
top: -(rippleSize / 2) + rippleY,
left: -(rippleSize / 2) + rippleX
};
useLayoutEffect(() => {
if (!inProp) {
setLeaving(true);
const timeoutValue = setTimeout(onExited, timeout);
return () => {
clearTimeout(timeoutValue);
};
}
}, [timeout, inProp, onExited]);
return (
<span style={style} className="ripple">
<span className={"ripple-child" + (leaving ? " leaving" : "")}></span>
</span>
);
};
Example 2
No state being managed.
const Ripple = ({
in: inProp,
onExited = () => {},
timeout,
rippleSize,
rippleY,
rippleX
}) => {
// const [leaving, setLeaving] = useState(false);
const style = {
width: rippleSize,
height: rippleSize,
top: -(rippleSize / 2) + rippleY,
left: -(rippleSize / 2) + rippleX
};
useLayoutEffect(() => {
if (!inProp) {
// setLeaving(true);
const timeoutValue = setTimeout(onExited, timeout);
return () => {
clearTimeout(timeoutValue);
};
}
}, [timeout, inProp, onExited]);
return (
<span style={style} className="ripple">
<span className={"ripple-child" + (!inProp ? " leaving" : "")}></span>
</span>
);
};
Apologies for the poor articulation. If there is additional information I can provide, please let me know.

React throws an error "Can't assign to property" scrollLeft "on 1: not an object" When trying use ref in custom function

I have a working animation of an object made with the "useRef" hook. Part of the code in this animation will be repeated several times, so I moved it into a separate function, but when I try to call this function, when rendering the component, I get the error "Can't assign to property" scrollLeft "on 1: not an object" what could be the problem?
Full code on codesandbox
https://codesandbox.io/s/peaceful-silence-bm6hx?file=/src/scroll.js
import React, {useState, useEffect, useRef} from 'react'
const Scrollable = props => {
const items = props.items;
let ref = useRef()
const [state, setState] = useState({
isScrolling:false,
clientX:0,
scrollX:0
})
const [touchStart, setTouchStart] = useState(0);
let frameId;
const onMouseDown = e =>{...}
const onMouseUp = e =>{
if(ref && ref.current && !ref.current.contains(e.target)) {
return;
}
e.preventDefault()
let touchShift = touchStart - state.clientX
let rez;
let shift;
if(touchShift > 0) {
shift = 300 - touchShift
rez = state.scrollX + shift
if(rez>2100){
rez =1800
cancelAnimationFrame(frameId)
}
let speed = shift / 20
let cur = state.scrollX
frameId = requestAnimationFrame(animate)
animate(cur,speed,rez)
}
}
const animate = (cur, speed,rez) => {
frameId = requestAnimationFrame(animate)
cur = cur + speed
ref.current.scrollLeft = cur.toFixed(2)
if (Math.round(cur) === rez) {
cancelAnimationFrame(frameId)
setState({
...state,
scrollX:rez,
isScrolling:false,
})
}
}
useEffect(() =>{
document.addEventListener('mousedown',onMouseDown)
document.addEventListener('mouseup',onMouseUp)
return () => {
document.removeEventListener('mousedown',onMouseDown)
document.removeEventListener('mouseup',onMouseUp)
}
})
useEffect(() =>{
ref.current = requestAnimationFrame(animate)
return () => {
cancelAnimationFrame(ref.current)
},[]})
return (
<div className={classes.charPage}>
<div
ref={ref}
onMouseDown={onMouseDown}
onMouseUp={onMouseUp}>
</div>
</div>
)
}
export default Scrollable;
This error means you're trying to set a property on a number. In your useEffect you're doing this:
ref.current = requestAnimationFrame(animate)
requestAnimationFrame returns, according to MDN:
A long integer value, the request id, that uniquely identifies the entry in the callback list. This is a non-zero value, but you may not make any other assumptions about its value.
But you're also using the same ref for your DOM element. After your useEffect runs it will have set your ref to the rAF id which is a number causing your error when you try to set the scrollLeft property on the ref.
What you can try next to solve this is to use 2 separate refs, one for the requestAnimationFrame and one for your DOM element.

Removing items from state by timer

There is a local state (hook), it has an array of four elements. There is a button on the screen to add a new element to this array. When a component is loaded, in useEffect called method that removes the first element from the state every 5 seconds. If you do not touch the button that adds a new element to the state, then everything works great. But if you start adding elements, the deletion works according to the previous state, only then the state with the new element is displayed. Tell me how to fix it so that everything works stably. I understand what needs to be sought in the direction of the life cycle, a conflict of states occurs, but I can not find a solution.
const Component = () => {
const [arr, setArr] = useState(['one', 'two', 'three', 'four']);
React.useEffect(() => {
console.log("render");
setTimeout(deleteElementFromArr, 5000)
});
const addNewElementToArr = () => {
let temp = arr.slice();
temp.push('newElement');
setArr(temp);
};
const deleteElementFromArr = () => {
if (arr.length > 0) {
console.log(arr);
let temp = arr.slice();
temp.splice(0, 1);
setArr(temp)
}
};
return (<div>
<div>
<Button onClick={addNewElementToArr}>add</Button>
</div>
<div style={{margiTop: '10px'}}>
{arr.map(a => `${a} `)}
</div>
</div>)
};
https://codepen.io/slava4ka/pen/WNNvrPV
In your useEffect hook, when the effect is finished, clear the timeout. When the state is changed, it will trigger again with the new value of the state.
React.useEffect(() => {
console.log("render");
const timer = setTimeout(deleteElementFromArr, 5000);
return () => clearTimeout(timer);
});

Categories