useHooks.iov4.1.2
DocsBlogGitHub
Hooks
No hooks found in any category.

useContactPicker

browser

Installation

npx usehooks-cli@latest add use-contact-picker

Description

A hook for accessing the Contact Picker API to select contacts with user permission. Allows web applications to access contact information from the user's device with explicit consent.

Return Type

UseContactPickerReturn
PropertyTypeDescription
contactsContact[]Array of selected contacts
isLoadingbooleanWhether contact selection is in progress
errorstring | nullError message if contact selection failed
isSupportedbooleanWhether the Contact Picker API is supported
availablePropertiesContactProperty[]Array of available contact properties that can be requested
selectContacts(properties: ContactProperty[], options?: UseContactPickerOptions) => Promise<Contact[]>Select contacts with specified properties
getAvailableProperties() => Promise<ContactProperty[]>Get available contact properties that can be requested
clearContacts() => voidClear the selected contacts

Examples

Basic Contact Selection

Select contacts with name and email properties

1import { useContactPicker } from '@usehooks/use-contact-picker'; 2 3function ContactSelector() { 4 const { 5 contacts, 6 isLoading, 7 error, 8 isSupported, 9 selectContacts, 10 clearContacts 11 } = useContactPicker(); 12 13 const handleSelectContacts = async () => { 14 try { 15 await selectContacts(['name', 'email']); 16 } catch (err) { 17 console.error('Failed to select contacts:', err); 18 } 19 }; 20 21 if (!isSupported) { 22 return <div>Contact Picker API not supported</div>; 23 } 24 25 return ( 26 <div> 27 <button onClick={handleSelectContacts} disabled={isLoading}> 28 {isLoading ? 'Selecting...' : 'Select Contacts'} 29 </button> 30 31 <button onClick={clearContacts}>Clear</button> 32 33 {error && <p>Error: {error}</p>} 34 35 {contacts.length > 0 && ( 36 <div> 37 <h3>Selected Contacts:</h3> 38 {contacts.map((contact, index) => ( 39 <div key={index}> 40 <p>Name: {contact.name?.[0] || 'N/A'}</p> 41 <p>Email: {contact.email?.[0] || 'N/A'}</p> 42 </div> 43 ))} 44 </div> 45 )} 46 </div> 47 ); 48}

Multiple Contact Selection

Select multiple contacts with various properties

1import { useContactPicker } from '@usehooks/use-contact-picker'; 2 3function MultiContactSelector() { 4 const { 5 contacts, 6 isLoading, 7 selectContacts, 8 getAvailableProperties, 9 availableProperties 10 } = useContactPicker(); 11 12 const handleSelectMultiple = async () => { 13 await selectContacts(['name', 'email', 'tel'], { multiple: true }); 14 }; 15 16 const handleGetProperties = async () => { 17 await getAvailableProperties(); 18 }; 19 20 return ( 21 <div> 22 <button onClick={handleGetProperties}> 23 Get Available Properties 24 </button> 25 26 <button onClick={handleSelectMultiple} disabled={isLoading}> 27 Select Multiple Contacts 28 </button> 29 30 {availableProperties.length > 0 && ( 31 <div> 32 <h3>Available Properties:</h3> 33 <ul> 34 {availableProperties.map(prop => ( 35 <li key={prop}>{prop}</li> 36 ))} 37 </ul> 38 </div> 39 )} 40 41 {contacts.length > 0 && ( 42 <div> 43 <h3>Selected Contacts ({contacts.length}):</h3> 44 {contacts.map((contact, index) => ( 45 <div key={index} style={{ border: '1px solid #ccc', padding: '10px', margin: '5px' }}> 46 <p><strong>Name:</strong> {contact.name?.join(', ') || 'N/A'}</p> 47 <p><strong>Email:</strong> {contact.email?.join(', ') || 'N/A'}</p> 48 <p><strong>Phone:</strong> {contact.tel?.join(', ') || 'N/A'}</p> 49 </div> 50 ))} 51 </div> 52 )} 53 </div> 54 ); 55}

Contact with Address

Select contacts including address information

1import { useContactPicker } from '@usehooks/use-contact-picker'; 2 3function AddressContactSelector() { 4 const { contacts, selectContacts, isLoading } = useContactPicker(); 5 6 const handleSelectWithAddress = async () => { 7 await selectContacts(['name', 'email', 'tel', 'address']); 8 }; 9 10 return ( 11 <div> 12 <button onClick={handleSelectWithAddress} disabled={isLoading}> 13 Select Contact with Address 14 </button> 15 16 {contacts.map((contact, index) => ( 17 <div key={index} style={{ border: '1px solid #ddd', padding: '15px', margin: '10px' }}> 18 <h4>{contact.name?.[0] || 'Unknown Contact'}</h4> 19 20 {contact.email && ( 21 <p><strong>Email:</strong> {contact.email.join(', ')}</p> 22 )} 23 24 {contact.tel && ( 25 <p><strong>Phone:</strong> {contact.tel.join(', ')}</p> 26 )} 27 28 {contact.address && contact.address.length > 0 && ( 29 <div> 30 <strong>Address:</strong> 31 {contact.address.map((addr, addrIndex) => ( 32 <div key={addrIndex} style={{ marginLeft: '10px' }}> 33 {addr.addressLine && <p>{addr.addressLine.join(', ')}</p>} 34 {addr.city && <p>{addr.city}, {addr.region} {addr.postalCode}</p>} 35 {addr.country && <p>{addr.country}</p>} 36 </div> 37 ))} 38 </div> 39 )} 40 </div> 41 ))} 42 </div> 43 ); 44}

Dependencies

react

Notes

  • Only available in browsers that support the Contact Picker API (Chrome 80+, Edge 80+)
  • Requires user gesture to initiate contact selection
  • Requires HTTPS in production environments
  • Available properties vary by platform and browser
  • User has full control over which contacts and properties to share
  • Contact data is not persisted and must be requested each time

Implementation

1'use client'; 2 3import { useState, useCallback } from "react"; 4 5type ContactProperty = "name" | "email" | "tel" | "address" | "icon"; 6 7interface ContactAddress { 8 country?: string; 9 addressLine?: string[]; 10 region?: string; 11 city?: string; 12 dependentLocality?: string; 13 postalCode?: string; 14 sortingCode?: string; 15} 16 17interface Contact { 18 name?: string[]; 19 email?: string[]; 20 tel?: string[]; 21 address?: ContactAddress[]; 22 icon?: Blob[]; 23} 24 25interface UseContactPickerOptions { 26 multiple?: boolean; 27} 28 29interface UseContactPickerReturn { 30 contacts: Contact[]; 31 isLoading: boolean; 32 error: string | null; 33 isSupported: boolean; 34 availableProperties: ContactProperty[]; 35 selectContacts: ( 36 properties: ContactProperty[], 37 options?: UseContactPickerOptions 38 ) => Promise<Contact[]>; 39 getAvailableProperties: () => Promise<ContactProperty[]>; 40 clearContacts: () => void; 41} 42 43// Extend Navigator interface for TypeScript 44declare global { 45 interface Navigator { 46 contacts?: { 47 select: ( 48 properties: ContactProperty[], 49 options?: { multiple?: boolean } 50 ) => Promise<Contact[]>; 51 getProperties: () => Promise<ContactProperty[]>; 52 }; 53 } 54} 55 56export const useContactPicker = (): UseContactPickerReturn => { 57 const [contacts, setContacts] = useState<Contact[]>([]); 58 const [isLoading, setIsLoading] = useState(false); 59 const [error, setError] = useState<string | null>(null); 60 const [availableProperties, setAvailableProperties] = useState< 61 ContactProperty[] 62 >([]); 63 64 // Check if Contact Picker API is supported 65 const isSupported = 66 typeof navigator !== "undefined" && 67 "contacts" in navigator && 68 typeof navigator.contacts?.select === "function"; 69 70 // Get available contact properties 71 const getAvailableProperties = useCallback(async (): Promise< 72 ContactProperty[] 73 > => { 74 if (!isSupported || !navigator.contacts?.getProperties) { 75 setError("Contact Picker API is not supported"); 76 return []; 77 } 78 79 try { 80 setError(null); 81 const properties = await navigator.contacts.getProperties(); 82 setAvailableProperties(properties); 83 return properties; 84 } catch (err) { 85 const errorMessage = 86 err instanceof Error 87 ? err.message 88 : "Failed to get available properties"; 89 setError(errorMessage); 90 return []; 91 } 92 }, [isSupported]); 93 94 // Select contacts with specified properties 95 const selectContacts = useCallback( 96 async ( 97 properties: ContactProperty[], 98 options: UseContactPickerOptions = {} 99 ): Promise<Contact[]> => { 100 if (!isSupported || !navigator.contacts?.select) { 101 setError("Contact Picker API is not supported"); 102 return []; 103 } 104 105 if (properties.length === 0) { 106 setError("At least one contact property must be specified"); 107 return []; 108 } 109 110 try { 111 setIsLoading(true); 112 setError(null); 113 114 const selectedContacts = await navigator.contacts.select(properties, { 115 multiple: options.multiple || false, 116 }); 117 118 setContacts(selectedContacts); 119 return selectedContacts; 120 } catch (err) { 121 let errorMessage = "Failed to select contacts"; 122 123 if (err instanceof Error) { 124 // Handle specific error cases 125 if (err.name === "InvalidStateError") { 126 errorMessage = "Contact picker is already open"; 127 } else if (err.name === "SecurityError") { 128 errorMessage = 129 "Contact picker requires user gesture and secure context"; 130 } else if (err.name === "NotSupportedError") { 131 errorMessage = "One or more requested properties are not supported"; 132 } else { 133 errorMessage = err.message; 134 } 135 } 136 137 setError(errorMessage); 138 return []; 139 } finally { 140 setIsLoading(false); 141 } 142 }, 143 [isSupported] 144 ); 145 146 // Clear selected contacts 147 const clearContacts = useCallback(() => { 148 setContacts([]); 149 setError(null); 150 }, []); 151 152 return { 153 contacts, 154 isLoading, 155 error, 156 isSupported, 157 availableProperties, 158 selectContacts, 159 getAvailableProperties, 160 clearContacts, 161 }; 162}; 163