Hooks
No hooks found in any category.
useContactPicker
browserInstallation
npx usehooks-cli@latest add use-contact-pickerDescription
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
| Property | Type | Description |
|---|---|---|
| contacts | Contact[] | Array of selected contacts |
| isLoading | boolean | Whether contact selection is in progress |
| error | string | null | Error message if contact selection failed |
| isSupported | boolean | Whether the Contact Picker API is supported |
| availableProperties | ContactProperty[] | 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 | () => void | Clear 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