Comment créer un formulaire de demande de devis dans React Native
Un formulaire de demande de devis React Native et Expo avec choix de la prestation, du budget et de l’échéance, qui vous envoie chaque demande par e-mail et la confirme automatiquement au client.
Mis à jour:
Ce tutoriel crée un écran de demande de devis dans React Native : le client choisit une prestation et un budget, ajoute une échéance et une description, et vous recevez un e-mail structuré. Une réponse automatique confirme la demande au client.
Il vous faut : un compte Frontmail gratuit, un projet Expo ou React Native et environ quinze minutes.
1. Connectez votre boîte mail et créez les modèles
- Services d’envoi → Ajouter un service : connectez votre boîte mail ou votre fournisseur, Définir par défaut, puis Vérifier et enregistrer.
- Modèles → Depuis la galerie → Demande de devis (catégorie Entreprise) → Utiliser ce modèle.
- Depuis la galerie → Réponse automatique : merci pour votre message → Utiliser ce
modèle. Définissez les valeurs par défaut de
company_nameetresponse_time, puis choisissez-le dans le modèle de devis sous Paramètres → Envoi → Modèle de réponse automatique.
Paramètres du modèle de devis : name, email, service, details (obligatoires) et phone,
company, budget, deadline (facultatifs, échéance au format YYYY-MM-DD). service et
budget n’acceptent que les valeurs listées dans Paramètres. Le code ci-dessous utilise les
valeurs du modèle anglais ; si vous créez le modèle en français, ce sont Site web, Boutique en
ligne, Application mobile, Conseil, Autre et Moins de 5 000 €, 5 000 – 20 000 €, 20 000 –
50 000 €, Plus de 50 000 €. L’application doit envoyer exactement les chaînes qui y figurent.
2. Autorisez les applications mobiles et activez le CAPTCHA
- Sécurité → Applications mobiles → Autoriser les applications mobiles → Enregistrer.
- Modèle de devis → Paramètres → Protection → Exiger un CAPTCHA (Turnstile) – l’application utilise le widget partagé de Frontmail, aucune clé nécessaire.
3. Installez
npx expo install @frontmail/react-native @react-native-async-storage/async-storage react-native-webview
4. Créez le formulaire
Pour que l’exemple n’ait pas de dépendances supplémentaires, les choix sont de simples boutons
« chips » et l’échéance se saisit au format YYYY-MM-DD. Remplacez-les par vos composants de
sélection préférés – seules comptent les valeurs que vous passez à send.
import { useRef, useState } from 'react';
import { Button, Pressable, Text, TextInput, View } from 'react-native';
import { FrontmailProvider, TurnstileWebView, useSendEmail, type TurnstileWebViewHandle } from '@frontmail/react-native';
const options = { publicKey: 'pk_…' };
const SERVICES = ['Website', 'E-shop', 'Mobile app', 'Consulting', 'Other'];
const BUDGETS = ['Under $5k', '$5k–20k', '$20k–50k', 'Over $50k'];
export default function App() {
return (
<FrontmailProvider options={options}>
<QuoteForm />
</FrontmailProvider>
);
}
function Choice({ values, value, onChange }: { values: string[]; value?: string; onChange(v?: string): void }) {
return (
<View style={{ flexDirection: 'row', flexWrap: 'wrap', gap: 8 }}>
{values.map((v) => (
<Pressable key={v} onPress={() => onChange(v === value ? undefined : v)} accessibilityState={{ selected: v === value }}>
<Text style={{ padding: 8, borderWidth: 1, borderRadius: 16, fontWeight: v === value ? '700' : '400' }}>{v}</Text>
</Pressable>
))}
</View>
);
}
function QuoteForm() {
const { send, status, error } = useSendEmail(null, 'tpl_…');
const [name, setName] = useState('');
const [email, setEmail] = useState('');
const [service, setService] = useState<string | undefined>(SERVICES[0]);
const [budget, setBudget] = useState<string>();
const [deadline, setDeadline] = useState('');
const [details, setDetails] = useState('');
const [token, setToken] = useState<string>();
const turnstile = useRef<TurnstileWebViewHandle>(null);
const onSend = async () => {
await send({ name, email, service, budget, deadline, details }, { turnstileToken: token });
setToken(undefined);
turnstile.current?.reset(); // a token works only once
};
if (status === 'sent' || status === 'held') return <Text>Thanks! We’ve emailed you a confirmation.</Text>;
return (
<View style={{ gap: 12, padding: 16 }}>
<TextInput placeholder="Name" value={name} onChangeText={setName} />
<TextInput placeholder="Email" value={email} onChangeText={setEmail} keyboardType="email-address" autoCapitalize="none" />
<Text>What do you need?</Text>
<Choice values={SERVICES} value={service} onChange={setService} />
<Text>Budget (optional)</Text>
<Choice values={BUDGETS} value={budget} onChange={setBudget} />
<TextInput placeholder="Deadline (YYYY-MM-DD, optional)" value={deadline} onChangeText={setDeadline} />
<TextInput placeholder="Project details" value={details} onChangeText={setDetails} multiline />
<TurnstileWebView ref={turnstile} onToken={setToken} />
<Button title="Request a quote" disabled={!token || !service || status === 'sending'} onPress={onSend} />
{error && <Text>{error.message}</Text>}
</View>
);
}
Les valeurs facultatives vides (pas de budget, échéance vide) sont automatiquement omises de l’e-mail.
5. Testez
Lancez l’application et envoyez une demande avec votre propre adresse e-mail. Vous recevez la
demande et la confirmation ; les deux apparaissent dans l’Historique. Les erreurs de
validation reviennent sous la forme invalid_template_params, avec le champ en cause dans
error.details.
Une demande et sa confirmation consomment deux crédits. Quand vos crédits sont épuisés, les demandes sont acceptées avec le statut
heldet envoyées dès que vous rechargez – le hook renvoiestatus === 'held'.
Étapes suivantes
- Gardez la liste de toutes les personnes qui vous ont sollicité : activez la Collecte des contacts dans les paramètres du modèle.
- Passez
{ locale: 'de' }comme second argument desendpour utiliser une variante linguistique du modèle.


