React Integration
Build SMS interfaces and testing workflows with React hooks and components.
Quick Setup
Create a React app with SMS capabilities in minutes.
1. Install Dependencies
# Create a new React app npx create-react-app sms-react-app cd sms-react-app # Install additional dependencies npm install axios # Install sms-dev globally npm install -g @relay/sms-dev # Start sms-dev in the background sms-dev
Custom Hooks
useSMS Hook
Custom hook for sending SMS messages and managing state
// hooks/useSMS.js import { useState, useCallback } from 'react'; import axios from 'axios'; const SMS_API_URL = 'http://localhost:4001/v1/messages'; export function useSMS() { const [messages, setMessages] = useState([]); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const sendMessage = useCallback(async (to, from, body) => { setLoading(true); setError(null); try { const response = await axios.post(SMS_API_URL, { to, from, body }); const newMessage = { id: response.data.id, to, from, body, timestamp: new Date().toISOString(), status: 'sent' }; setMessages(prev => [...prev, newMessage]); return newMessage; } catch (err) { const errorMessage = err.response?.data?.error || 'Failed to send SMS'; setError(errorMessage); throw new Error(errorMessage); } finally { setLoading(false); } }, []); const clearMessages = useCallback(() => { setMessages([]); setError(null); }, []); const clearError = useCallback(() => { setError(null); }, []); return { messages, loading, error, sendMessage, clearMessages, clearError }; }
useConversation Hook
Manage conversation flows and message history
// hooks/useConversation.js import { useState, useEffect, useCallback } from 'react'; import { useSMS } from './useSMS'; export function useConversation(phoneNumber) { const { sendMessage, loading, error } = useSMS(); const [conversation, setConversation] = useState([]); const [typing, setTyping] = useState(false); // Simulate incoming messages for demo const simulateReply = useCallback((replyText, delay = 2000) => { setTyping(true); setTimeout(() => { const reply = { id: `sim_${Date.now()}`, to: '+1987654321', from: phoneNumber, body: replyText, timestamp: new Date().toISOString(), direction: 'inbound' }; setConversation(prev => [...prev, reply]); setTyping(false); }, delay); }, [phoneNumber]); const sendConversationMessage = useCallback(async (body) => { try { const message = await sendMessage( phoneNumber, '+1987654321', body ); const conversationMessage = { ...message, direction: 'outbound' }; setConversation(prev => [...prev, conversationMessage]); // Auto-reply based on message content if (body.toLowerCase().includes('hello')) { simulateReply('Hi! How can I help you today?'); } else if (body.toLowerCase().includes('help')) { simulateReply('Available commands: HELP, INFO, TIME, JOKE'); } else if (body.toLowerCase().includes('time')) { simulateReply(`Current time: ${new Date().toLocaleTimeString()}`); } else { simulateReply('Thanks for your message! This is an automated response.'); } return conversationMessage; } catch (err) { console.error('Failed to send message:', err); throw err; } }, [phoneNumber, sendMessage, simulateReply]); const clearConversation = useCallback(() => { setConversation([]); }, []); return { conversation, loading, error, typing, sendMessage: sendConversationMessage, clearConversation }; }
React Components
SMS Form Component
Reusable form component for sending SMS messages
// components/SMSForm.jsx import React, { useState } from 'react'; import { useSMS } from '../hooks/useSMS'; export function SMSForm({ onMessageSent }) { const [to, setTo] = useState(''); const [from, setFrom] = useState('+1987654321'); const [body, setBody] = useState(''); const { sendMessage, loading, error } = useSMS(); const handleSubmit = async (e) => { e.preventDefault(); if (!to || !from || !body) { alert('Please fill in all fields'); return; } try { const message = await sendMessage(to, from, body); setTo(''); setBody(''); onMessageSent?.(message); } catch (err) { // Error is handled by the hook } }; return ( <form onSubmit={handleSubmit} className="space-y-4"> <div> <label htmlFor="to" className="block text-sm font-medium text-gray-700"> To (Phone Number) </label> <input type="tel" id="to" value={to} onChange={(e) => setTo(e.target.value)} placeholder="+1234567890" className="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500" disabled={loading} /> </div> <div> <label htmlFor="from" className="block text-sm font-medium text-gray-700"> From (Your Number) </label> <input type="tel" id="from" value={from} onChange={(e) => setFrom(e.target.value)} className="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500" disabled={loading} /> </div> <div> <label htmlFor="body" className="block text-sm font-medium text-gray-700"> Message </label> <textarea id="body" value={body} onChange={(e) => setBody(e.target.value)} placeholder="Enter your message here..." rows={3} maxLength={160} className="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500" disabled={loading} /> <p className="mt-1 text-sm text-gray-500"> {body.length}/160 characters </p> </div> {error && ( <div className="p-3 bg-red-100 border border-red-400 text-red-700 rounded"> {error} </div> )} <button type="submit" disabled={loading || !to || !from || !body} className="w-full flex justify-center py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 disabled:opacity-50 disabled:cursor-not-allowed" > {loading ? 'Sending...' : 'Send SMS'} </button> </form> ); }
Conversation Component
Chat-like interface for testing conversation flows
// components/Conversation.jsx import React, { useState, useRef, useEffect } from 'react'; import { useConversation } from '../hooks/useConversation'; export function Conversation({ phoneNumber = '+1234567890' }) { const [newMessage, setNewMessage] = useState(''); const { conversation, loading, typing, sendMessage, clearConversation } = useConversation(phoneNumber); const messagesEndRef = useRef(null); const scrollToBottom = () => { messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' }); }; useEffect(() => { scrollToBottom(); }, [conversation, typing]); const handleSend = async (e) => { e.preventDefault(); if (!newMessage.trim() || loading) return; try { await sendMessage(newMessage.trim()); setNewMessage(''); } catch (err) { console.error('Failed to send message:', err); } }; const formatTime = (timestamp) => { return new Date(timestamp).toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit' }); }; return ( <div className="flex flex-col h-96 border border-gray-300 rounded-lg overflow-hidden"> {/* Header */} <div className="bg-blue-600 text-white p-3 flex justify-between items-center"> <h3 className="font-semibold">Conversation with {phoneNumber}</h3> <button onClick={clearConversation} className="text-xs bg-blue-700 hover:bg-blue-800 px-2 py-1 rounded" > Clear </button> </div> {/* Messages */} <div className="flex-1 p-4 overflow-y-auto bg-gray-50"> {conversation.length === 0 && ( <div className="text-center text-gray-500 py-8"> <p>No messages yet. Send a message to start the conversation!</p> </div> )} {conversation.map((message) => ( <div key={message.id} className={`flex mb-4 ${ message.direction === 'outbound' ? 'justify-end' : 'justify-start' }`} > <div className={`max-w-xs lg:max-w-md px-4 py-2 rounded-lg ${ message.direction === 'outbound' ? 'bg-blue-600 text-white rounded-br-none' : 'bg-white border border-gray-200 text-gray-800 rounded-bl-none' }`} > <p className="text-sm whitespace-pre-wrap">{message.body}</p> <p className={`text-xs mt-1 ${ message.direction === 'outbound' ? 'text-blue-100' : 'text-gray-500' }`}> {formatTime(message.timestamp)} </p> </div> </div> ))} {typing && ( <div className="flex justify-start mb-4"> <div className="bg-white border border-gray-200 rounded-lg rounded-bl-none px-4 py-2 max-w-xs"> <div className="flex space-x-1"> <div className="w-2 h-2 bg-gray-400 rounded-full animate-bounce"></div> <div className="w-2 h-2 bg-gray-400 rounded-full animate-bounce" style={{ animationDelay: '0.1s' }}></div> <div className="w-2 h-2 bg-gray-400 rounded-full animate-bounce" style={{ animationDelay: '0.2s' }}></div> </div> </div> </div> )} <div ref={messagesEndRef} /> </div> {/* Input */} <form onSubmit={handleSend} className="p-3 border-t border-gray-200 bg-white"> <div className="flex space-x-2"> <input type="text" value={newMessage} onChange={(e) => setNewMessage(e.target.value)} placeholder="Type a message..." className="flex-1 px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500" disabled={loading} /> <button type="submit" disabled={!newMessage.trim() || loading} className="px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 disabled:opacity-50 disabled:cursor-not-allowed" > Send </button> </div> </form> </div> ); }
Complete App Example
Put it all together in your main App component
// App.js import React, { useState } from 'react'; import { SMSForm } from './components/SMSForm'; import { Conversation } from './components/Conversation'; import './App.css'; function App() { const [selectedPhone, setSelectedPhone] = useState('+1234567890'); const [messagesSent, setMessagesSent] = useState(0); const handleMessageSent = (message) => { console.log('Message sent:', message); setMessagesSent(prev => prev + 1); }; return ( <div className="App"> <header className="bg-blue-600 text-white p-6"> <h1 className="text-3xl font-bold">SMS Development Dashboard</h1> <p className="mt-2 text-blue-100"> Test your SMS applications with sms-dev </p> </header> <main className="container mx-auto px-4 py-8"> <div className="grid md:grid-cols-2 gap-8"> {/* SMS Form */} <div className="space-y-6"> <div className="bg-white p-6 rounded-lg shadow-md"> <h2 className="text-xl font-semibold mb-4">Send SMS</h2> <SMSForm onMessageSent={handleMessageSent} /> </div> <div className="bg-white p-6 rounded-lg shadow-md"> <h3 className="text-lg font-semibold mb-2">Statistics</h3> <p className="text-gray-600">Messages sent: {messagesSent}</p> <p className="text-gray-600"> Virtual Phone: <a href="http://localhost:4000" target="_blank" rel="noopener noreferrer" className="text-blue-600 hover:underline ml-1" > http://localhost:4000 </a> </p> </div> </div> {/* Conversation */} <div className="space-y-6"> <div className="bg-white p-6 rounded-lg shadow-md"> <div className="flex justify-between items-center mb-4"> <h2 className="text-xl font-semibold">Test Conversation</h2> <select value={selectedPhone} onChange={(e) => setSelectedPhone(e.target.value)} className="px-3 py-1 border border-gray-300 rounded text-sm" > <option value="+1234567890">+1 (234) 567-890</option> <option value="+1555123456">+1 (555) 123-456</option> <option value="+1999888777">+1 (999) 888-777</option> </select> </div> <Conversation phoneNumber={selectedPhone} /> </div> </div> </div> </main> </div> ); } export default App;
Running the Example
Step-by-Step Instructions
1
Start sms-dev
Run sms-dev
in your terminal
2
Start your React app
Run npm start
3
Test the interface
Use the forms to send messages and see them in the Virtual Phone