I'm new to React (and programming in general) and have come across an issue. I have created this component and using react-copy-to-clipboard
package, onCopy
I would like to copy a string, set copied
to true and then after a couple of seconds set copied
to false. When copied
is true I want a div with some text to be displayed and when copied
is set to false, I want it to disappear.
I have tried using setTimeout
without success, as you can see in the code below and I suppose it's not working as I wish as the JSX doesn't re-render when the copied
state is false again. I thought of using promises but couldn't figure out how to do that when I want to return JSX.
import React, { useState, useCallback } from 'react';
import { CopyToClipboard } from 'react-copy-to-clipboard';
import styled from 'styled-components';
const ClipBoard = () => {
const [copied, setCopied] = useState(false);
const onCopy = useCallback(() => {
setTimeout(setCopied(false), 2000)
setCopied(true);
console.log(copied)
}, [copied]);
return (
<div className="app">
<section className="section">
{copied ? <div><p>Copied</p></div> : null}
<CopyToClipboard onCopy={onCopy} text="Text I want to copy">
<button type="button">
<span className="sr-only">
E-mail
</span>
Text I want to copy
</button>
</CopyToClipboard>
</section>
</div>
);
}
export default ClipBoard;
CodePudding user response:
Issue
The code is immediately invoking the state update to enqueue the copied
state value to false.
setTimeout(
setCopied(false), // <-- immediately called!
2000
);
setTimeout
expects a callback function that will be invoked when the timeout expires.
Solution
Pass an anonymous function to setTimeout
to be called when the timeout expires. Since React state updates are enqueued and asynchronously processed, the console.log(copied)
will only log the unupdated copied
state value closed over in callback scope from the current render cycle. If you want to log state values use the useEffect
hook. Also, you should consider the edge case where the component unmounts prior to the timeout expiring and clear any running timers.
Full Example:
const ClipBoard = () => {
const [copied, setCopied] = useState(false);
const timerRef = useRef(); // React ref for timer reference
useEffect(() => {
// Return cleanup function to clear timer when unmounting
return () => {
clearTimeout(timerRef.current);
};
}, []);
useEffect(() => {
// Effect to log any copied state updates
console.log(copied);
}, [copied]);
const onCopy = useCallback(() => {
timerRef.current = setTimeout( // save timer reference
() => setCopied(false), // function callback to update state
2000,
);
setCopied(true);
}, []); // empty dependency array
return (
<div className="app">
<section className="section">
{copied && <div><p>Copied</p></div>}
<CopyToClipboard onCopy={onCopy} text="Text I want to copy">
<button type="button">
<span className="sr-only">
E-mail
</span>
Text I want to copy
</button>
</CopyToClipboard>
</section>
</div>
);
}