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
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.
-
selectData
to keep the data of all selected items. -
isCopy
boolean to show the table once the copy button is clicked. -
data
: This is the simplest data that I will be using to render the lists. You can make use of complex data coming from the backend. And render the lists dynamically. -
tableData
: This state is used when the user selects a name the name gets added to this state as an array of objects.
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);
}
}
}
-
The
range
andsel
variables are declared. These variables will be used to manipulate the selection and range of objects required for copying to the clipboard. -
The code checks if the browser supports the
createRange
andgetSelection
methods. If so, it proceeds with the range and selection manipulation steps:-
A range is created using
document.createRange()
, and the current selection is obtained usingwindow.getSelection()
. -
The existing ranges in the selection are removed using
sel.removeAllRanges()
. -
The code tries to select the contents of the
el
element (the table) usingrange.selectNodeContents(el)
. If an error occurs, it selects the entireel
element usingrange.selectNode(el)
. -
The selected range is added to the selection using
sel.addRange(range)
. -
Finally, the
document.execCommand('copy')
is executed to copy the selected content to the clipboard.
-
-
If the browser does not support
createRange
andgetSelection
, it falls back to using thecreateTextRange
method available onbody
:-
A range is created using
body.createTextRange()
. -
The range is moved to encompass the text within the
el
element usingrange.moveToElementText(el)
. -
The range is selected using range.select().
-
The
Copy
command is executed on the range usingrange.execCommand('Copy')
.
-
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>
)
}