sultan.h
sultan.h

Reputation: 405

copying to clipboard react

I'm trying to copy a text to clipboard using a button, the code working fine but the problem is copying another post text not the targeted one! I have multi posts using map. and each post has a text and a button, the issue now is when i'm hitting the button it copies the first post text!

The code for Post.jsx

export default function Post({ post }) {

    var cpnBtn = document.getElementById("cpnBtn");
    var cpnCode = document.getElementById("cpnCode");

      const CopyCode = () => {
        navigator.clipboard.writeText(cpnCode.innerHTML);
        cpnBtn.innerHTML = "COPIED";
        setTimeout(function(){
          cpnBtn.innerHTML = "COPY CODE";
        }, 3000);
      }

  return (
    <div className="coupon">
    <div className="coupon-container">
      <div className="coupon-card">
      
      <div className="postInfo">
      {post.photo && <img className="postImg" src={post.photo} alt="" />}

        <div className="postTitle">
          <span >{post.shop}</span>
        </div>

        <div className="postTitle">
          <span >{post.title}</span>
        </div>
        
        <div className="coupon-row">

        <span id="cpnCode">{post.coupon}</span>

        <button id="cpnBtn" onClick={CopyCode}>COPY CODE</button>

        </div>
      </div>
      </div>
    </div>
    </div>
  );
}

The code for Posts.jsx

export default function Posts({ posts }) {
  return (
    <div className="posts">
      {posts.map((p) => (
        <Post post={p} />
      ))}
    </div>
  );
}

Upvotes: 0

Views: 6971

Answers (1)

Besnik Kor&#231;a
Besnik Kor&#231;a

Reputation: 1094

The reason why it's copying always the first coupon is that you're rendering multiple posts and all have a coupon with id="cpnCode" so it will always use the top one. Besides that, I've explained below the proper way to implement such functionality in react.

The easiest way to solve this would be to use the useState of react to hold the text of the button in it. When using react we try to avoid mutating DOM elements manually instead we rely on to react to perform these updates.

Also, there's no need to take the coupon value from the coupon HTML element if you already have access to the post.

writeText is a Promise that's why I added the then block and the catch so you can see the error in your console in case something goes wrong.

import { useState } from "react";

export default function Post({ post }) {
  const [bttnText, setBttnText] = useState("COPY CODE");

  const copyCode = () => {
    navigator.clipboard
      .writeText(post.coupon)
      .then(() => {
        setBttnText("COPIED");
        setTimeout(function () {
          setBttnText("COPY CODE");
        }, 3000);
      })
      .catch((err) => {
        console.log(err.message);
      });
  };

  return (
    <div className="coupon">
      <div className="coupon-container">
        <div className="coupon-card">
          <div className="postInfo">
            {post.photo && <img className="postImg" src={post.photo} alt="" />}

            <div className="postTitle">
              <span>{post.shop}</span>
            </div>

            <div className="postTitle">
              <span>{post.title}</span>
            </div>

            <div className="coupon-row">
              <span>{post.coupon}</span>

              <button onClick={copyCode}>{bttnText}</button>
            </div>
          </div>
        </div>
      </div>
    </div>
  );
}

Alterantively in case you really need to access the htmlElements it can be done with useRef e.g

import { useState, useRef } from "react";

export default function Post({ post }) {
  const [bttnText, setBttnText] = useState("COPY CODE");
  const couponRef = useRef();

  const copyCode = () => {
    // just a safety mechanism to make sure that
    // the references found the DOM elmenets button and coupon
    // before trying to use them
    if (!couponRef.current) return;


    navigator.clipboard
      // again doesn't make sense to use here
      // couponRef.current.innerHTML
      // since you got access to post.coupon
      .writeText(couponRef.current.innerHTML)
      .then(() => {
        setBttnText("COPIED");
        setTimeout(function () {
          setBttnText("COPY CODE");
        }, 3000);
      })
      .catch((err) => {
        console.log(err.message);
      });
  };

  return (
    <div className="coupon">
      <div className="coupon-container">
        <div className="coupon-card">
          <div className="postInfo">
            {post.photo && <img className="postImg" src={post.photo} alt="" />}

            <div className="postTitle">
              <span>{post.shop}</span>
            </div>

            <div className="postTitle">
              <span>{post.title}</span>
            </div>

            <div className="coupon-row">
              <span ref={couponRef}>{post.coupon}</span>

              <button onClick={copyCode}>{bttnText}</button>
            </div>
          </div>
        </div>
      </div>
    </div>
  );
}

Upvotes: 4

Related Questions