Home > Mobile >  Have a function return JSX, then setState
Have a function return JSX, then setState

Time:10-21

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>
  );
}
  • Related