Cross-platform clipboard management for React Native and Expo applications with text, image, and URL support
—
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Pending
The risk profile of this skill
Clipboard change event listeners for detecting and responding to clipboard content changes. Event handling is supported on iOS and Android platforms but is a no-op on Web.
Adds a listener that fires whenever the content of the user's clipboard changes.
/**
* Adds a listener that will fire whenever the content of the user's clipboard changes.
* This method is a no-op on Web.
* @param listener - Callback to execute when listener is triggered. The callback receives
* a single argument containing information about clipboard contents.
* @returns EventSubscription object to remove the listener
*/
function addClipboardListener(listener: (event: ClipboardEvent) => void): EventSubscription;
interface ClipboardEvent {
/**
* @deprecated Returns empty string. Use getStringAsync() instead to retrieve clipboard content.
*/
content: string;
/** An array of content types that are available on the clipboard */
contentTypes: ContentType[];
}
enum ContentType {
PLAIN_TEXT = 'plain-text',
HTML = 'html',
IMAGE = 'image',
URL = 'url' // iOS only
}
interface EventSubscription {
remove(): void;
}Usage Examples:
import * as Clipboard from "expo-clipboard";
// Basic clipboard change detection
const subscription = Clipboard.addClipboardListener(({ contentTypes }) => {
console.log("Clipboard changed, content types:", contentTypes);
if (contentTypes.includes(Clipboard.ContentType.PLAIN_TEXT)) {
console.log("Clipboard contains text");
}
if (contentTypes.includes(Clipboard.ContentType.IMAGE)) {
console.log("Clipboard contains image");
}
});
// Comprehensive clipboard monitoring
const subscription = Clipboard.addClipboardListener(async ({ contentTypes }) => {
if (contentTypes.includes(Clipboard.ContentType.PLAIN_TEXT)) {
const text = await Clipboard.getStringAsync();
console.log("Text copied:", text.substring(0, 50) + "...");
}
if (contentTypes.includes(Clipboard.ContentType.IMAGE)) {
const image = await Clipboard.getImageAsync({ format: 'png' });
if (image) {
console.log(`Image copied: ${image.size.width}x${image.size.height}`);
}
}
if (contentTypes.includes(Clipboard.ContentType.URL)) {
// iOS only
const url = await Clipboard.getUrlAsync();
console.log("URL copied:", url);
}
});
// React component integration
const ClipboardMonitor = () => {
const [clipboardContent, setClipboardContent] = useState<string>("");
useEffect(() => {
const subscription = Clipboard.addClipboardListener(async ({ contentTypes }) => {
if (contentTypes.includes(Clipboard.ContentType.PLAIN_TEXT)) {
const text = await Clipboard.getStringAsync();
setClipboardContent(text);
}
});
return () => subscription.remove();
}, []);
return <Text>Last copied: {clipboardContent}</Text>;
};Removes a previously added clipboard listener using its subscription.
/**
* Removes the listener added by addClipboardListener. This method is a no-op on Web.
* @param subscription - The subscription to remove (created by addClipboardListener)
*/
function removeClipboardListener(subscription: EventSubscription): void;Usage Examples:
import * as Clipboard from "expo-clipboard";
// Manual listener management
const subscription = Clipboard.addClipboardListener(() => {
console.log("Clipboard changed!");
});
// Remove listener when done
Clipboard.removeClipboardListener(subscription);
// Alternative using subscription.remove() directly
const subscription = Clipboard.addClipboardListener(() => {
console.log("Clipboard changed!");
});
subscription.remove(); // Equivalent to removeClipboardListener(subscription)
// Component cleanup pattern
const useClipboardListener = (callback: (event: ClipboardEvent) => void) => {
useEffect(() => {
const subscription = Clipboard.addClipboardListener(callback);
return () => {
// Either method works for cleanup
Clipboard.removeClipboardListener(subscription);
// or: subscription.remove();
};
}, [callback]);
};The contentTypes array indicates what types of content are currently in the clipboard:
// Example event object
{
content: "", // Always empty string (deprecated)
contentTypes: [
Clipboard.ContentType.PLAIN_TEXT,
Clipboard.ContentType.HTML
]
}const handleClipboardChange = ({ contentTypes }: ClipboardEvent) => {
// Check for specific content types
const hasText = contentTypes.includes(Clipboard.ContentType.PLAIN_TEXT);
const hasHtml = contentTypes.includes(Clipboard.ContentType.HTML);
const hasImage = contentTypes.includes(Clipboard.ContentType.IMAGE);
const hasUrl = contentTypes.includes(Clipboard.ContentType.URL); // iOS only
// Multiple content types possible
if (hasText && hasHtml) {
console.log("Clipboard contains rich text with HTML formatting");
}
if (hasText && hasImage) {
console.log("Clipboard contains both text and image");
}
};addClipboardListener and removeClipboardListener do nothingimport * as Clipboard from "expo-clipboard";
import { Platform } from 'react-native';
const useClipboardMonitoring = () => {
const [clipboardState, setClipboardState] = useState({
hasText: false,
hasImage: false,
lastUpdate: Date.now()
});
useEffect(() => {
if (Platform.OS === 'web') {
// Web: Use polling or manual checks
const checkClipboard = async () => {
const hasText = await Clipboard.hasStringAsync();
const hasImage = await Clipboard.hasImageAsync();
setClipboardState({
hasText,
hasImage,
lastUpdate: Date.now()
});
};
// Manual check on component focus
checkClipboard();
return () => {}; // No cleanup needed
} else {
// Native: Use event listeners
const subscription = Clipboard.addClipboardListener(async ({ contentTypes }) => {
setClipboardState({
hasText: contentTypes.includes(Clipboard.ContentType.PLAIN_TEXT),
hasImage: contentTypes.includes(Clipboard.ContentType.IMAGE),
lastUpdate: Date.now()
});
});
return () => subscription.remove();
}
}, []);
return clipboardState;
};Event listener operations may throw UnavailabilityError if clipboard functionality is not available.
import * as Clipboard from "expo-clipboard";
import { UnavailabilityError } from 'expo-modules-core';
try {
const subscription = Clipboard.addClipboardListener(() => {
console.log("Clipboard changed");
});
} catch (error) {
if (error instanceof UnavailabilityError) {
console.log("Clipboard event listeners not available");
}
}