How to Implement Copy to Clipboard with multiselect list items?

By Prajwal Haniya

Techletter 35 | June 26, 2023

Copy to clipboard is one of the most important features for any tool with lists of products/ people. Imagine you want to share the details of a product or contact details with someone else or if you want the details in a different document, then copying by selecting can be a real pain. So, we automate the process of copying to the clipboard through which you can copy one or more items with the help of a checkbox and a button.

The below image shows the app that is completed

1

Now, let’s build the above app to demonstrate the copy-to-clipboard functionality.

import { useState } from 'react';

function App() {
  const [selectedData, setSelectedData] = useState([]);
  const [isCopy, setIsCopy] = useState(false);
  const [data] = useState([
    {
      id: 1,
      name: 'Prajwal'
    },
    {
      id: 2,
      name: 'Arjun'
    }
  ]);
  const [tableData, setTableData] = useState(null);

    return (
        <>
            {
                // All other functionalities goes here
            }
        </>
    )
}

export default App;

In the above app component, we can see that, I have made use of 4 different states.

Now let us write the function for selecting the user and populating the state selectedData with the selected user. Both selection and deselect should happen accordingly.

function App() {
    // All the states as shown above
    const mainSelect = async data => {
    if (selectedData.length > 0) {
        let count = 0;
        for (let i = 0; i < selectedData.length; i++) {
            const contact = selectedData[i];
            if (contact.id === data.id) {
                // If unchecked, delete the added data
                const newData = selectedData;
                newData.splice(i, 1);
                setSelectedData(newData);
                break;
            }
            count++;
            if (selectedData.length === count) {
                const newDataName = selectedData;
                newDataName.push(data);
                setSelectedData(newDataName);
                break;
            }
        }
    } else {
        // first setSelectedData, this runs at first
        const newDataSelectedData = selectedData;
        newDataSelectedData.push(data);
        console.log({ newDataSelectedData });
        setSelectedData(newDataSelectedData);
    }
  }
    return (
        //... 
    )
}

// We need to render the lists so you can make use of the below component

function ListRow({ item, mainSelect }) {
  const handleSelect = async () => {
    // pass the id and store it in state as array
    mainSelect(item);
  };
  return (
  <TableRow hover>
      <TableCell className="text-left text-primary cursor" style={{ display: '-webkit-box', maxWidth: '100px' }}>
          <Checkbox
              onChange={handleSelect}
              color="primary"
              size="small"
              inputProps={{ 'aria-label': 'secondary checkbox' }}
          />
          {item.name}
      </TableCell>
  </TableRow>
  )
}

In the ListRow component, we are passing mainSelect function as the props. Whenever we select/ click on a checkbox the mainSelect function will be called with the respective id.

Now we need to create a function for copying to the clipboard after the user selects the list.

function App() {
    // all states

    // mainSelect function

    const copyToClipBoard = async () => {
    try {
        if (selectedData && selectedData.length) {
            // boolean which opens the table
            setIsCopy(true);
            // sets the data | you can make use of API call if the data is coming from the backend.
            // set the table data once you recieve the data from the backend
            setTableData(selectedData);
            setTimeout(() => {
                const el = document.getElementById('copyToClipboard');
                const body = document.body;
                let range;
                let sel;
                if (document.createRange && window.getSelection) {
                    range = document.createRange();
                    sel = window.getSelection();
                    sel.removeAllRanges();
                    try {
                        range.selectNodeContents(el);
                        sel.addRange(range);
                    } catch (e) {
                        range.selectNode(el);
                        sel.addRange(range);
                    }
                    document.execCommand('copy');
                } else if (body.createTextRange) {
                    range = body.createTextRange();
                    range.moveToElementText(el);
                    range.select();
                    range.execCommand('Copy');
                }
                setTimeout(() => {
                    setIsCopy(false);
                    setTableData(null)
                }, 1000);
            }, 400);
        }
    } catch (error) {
        console.log('Error while fetching the details in handleCallSelect', error);
    }
  }
}

Hope, this was a slightly complex function. But, the rest of the code is almost self explanatory.

The complete code is as follows:

import { useState } from 'react';
import { Table, TableHead, TableBody, TableRow, TableCell, Checkbox, Button } from '@mui/material';


function App() {

  const [selectedData, setSelectedData] = useState([]);
  const [isCopy, setIsCopy] = useState(false);
  const [data] = useState([
    {
      id: 1,
      name: 'Prajwal'
    },
    {
      id: 2,
      name: 'Arjun'
    }
  ]);

  const [tableData, setTableData] = useState(null);

  const mainSelect = async data => {
    console.log({ data });
    if (selectedData.length > 0) {
        let count = 0;
        for (let i = 0; i < selectedData.length; i++) {
            const contact = selectedData[i];
            if (contact.id === data.id) {
                const newData = selectedData;
                newData.splice(i, 1);
                setSelectedData(newData);
                break;
            }
            count++;
            if (selectedData.length === count) {
                const newDataName = selectedData;
                newDataName.push(data);
                setSelectedData(newDataName);
                break;
            }
        }
    } else {
        const newDataSelectedData = selectedData;
        newDataSelectedData.push(data);
        console.log({ newDataSelectedData });
        setSelectedData(newDataSelectedData);
    }
  }

  const copyToClipBoard = async () => {
    try {
        console.log('Inside copy to clipboard');
        if (selectedData && selectedData.length) {
            setIsCopy(true);
            setTableData(selectedData);
            setTimeout(() => {
                const el = document.getElementById('copyToClipboard');
                const body = document.body;
                let range;
                let sel;
                if (document.createRange && window.getSelection) {
                    range = document.createRange();
                    sel = window.getSelection();
                    sel.removeAllRanges();
                    try {
                        range.selectNodeContents(el);
                        sel.addRange(range);
                    } catch (e) {
                        range.selectNode(el);
                        sel.addRange(range);
                    }
                    document.execCommand('copy');
                } else if (body.createTextRange) {
                    range = body.createTextRange();
                    range.moveToElementText(el);
                    range.select();
                    range.execCommand('Copy');
                }
                setTimeout(() => {
                    setIsCopy(false);
                    setTableData(null)
                }, 1000);
            }, 400);
        }
    } catch (error) {
        console.log('Error while fetching the details in handleCallSelect', error);
    }
  }

  return (
    <>
      <div>
            <div className="myscroll bg-white pl-2 pr-2 pb-2 mt-2">
                <Table stickyHeader className="mt-2">
                    <TableHead>
                        <TableRow>
                            <TableCell>Name</TableCell>
                        </TableRow>
                    </TableHead>
                    <TableBody>
                        {data.map((item, index) => (
                          <ListRow item={item} mainSelect={mainSelect} key={index} />
                          ))}
                    </TableBody>
                    <Button variant="outlined" onClick={copyToClipBoard}>Copy to clipboard</Button>
                </Table>
            </div>
            {
              isCopy ?
              <table id="copyToClipboard" border="1">
              <thead>
                <tr>
                  <th>Name</th>
                </tr>
              </thead>
              <tbody>
                {tableData.map((row, index) => (
                  <tr key={index}>
                    <td>
                      <span>Name: {row.name}</span>
                      <br />
                    </td>
                  </tr>
                ))}
              </tbody>
              </table>
              :
              null
            }
        </div>
    </>
  )
  }

export default App;

function ListRow({ item, mainSelect }) {
  const handleSelect = async () => {
    // pass the id and store it in state as array
    mainSelect(item);
  };
  return (
  <TableRow hover>
      <TableCell className="text-left text-primary cursor" style={{ display: '-webkit-box', maxWidth: '100px' }}>
          <Checkbox
              onChange={handleSelect}
              color="primary"
              size="small"
              inputProps={{ 'aria-label': 'secondary checkbox' }}
          />
          {item.name}
      </TableCell>
  </TableRow>
  )
}