Hooks
No hooks found in any category.
useContactPicker
browserInstallation
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
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