Networking and Data
WebF provides standard web APIs for networking and data storage, allowing you to connect your application to backend services and persist data locally.
Fetching Data from the Internet
WebF provides the standard fetch API and supports popular networking libraries, making it easy to connect to backend services.
Using the Fetch API
The modern fetch API is fully supported for making network requests. You can use it for GET, POST, and other HTTP methods, just like in a browser.
GET Request
// Simple GET request
async function getData() {
try {
const response = await fetch('https://api.example.com/data');
const data = await response.json();
console.log(data);
return data;
} catch (error) {
console.error('Error fetching data:', error);
}
}POST Request
async function postData(url = '', data = {}) {
try {
const response = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(data)
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return await response.json();
} catch (error) {
console.error('Error posting data:', error);
throw error;
}
}
// Usage
postData('https://api.example.com/users', { username: 'webf', email: 'user@example.com' })
.then(data => console.log('Success:', data));Other HTTP Methods
// PUT request
async function updateData(url, data) {
const response = await fetch(url, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
});
return response.json();
}
// DELETE request
async function deleteData(url) {
const response = await fetch(url, {
method: 'DELETE'
});
return response.json();
}
// PATCH request
async function patchData(url, data) {
const response = await fetch(url, {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
});
return response.json();
}Using in React Components
Here’s how you might use fetch inside a React component:
import { useState, useEffect } from 'react';
function DataFetcher() {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
fetch('https://api.example.com/data')
.then(response => {
if (!response.ok) {
throw new Error('Network response was not ok');
}
return response.json();
})
.then(data => {
setData(data);
setLoading(false);
})
.catch(error => {
setError(error.message);
setLoading(false);
});
}, []); // Empty dependency array means this runs once on mount
if (loading) return <div>Loading...</div>;
if (error) return <div>Error: {error}</div>;
if (!data) return null;
return <pre>{JSON.stringify(data, null, 2)}</pre>;
}Using Networking Libraries
Because the underlying fetch API is supported, popular networking libraries work out of the box.
Axios
npm install axiosimport axios from 'axios';
// GET request
const response = await axios.get('https://api.example.com/users');
console.log(response.data);
// POST request
const newUser = await axios.post('https://api.example.com/users', {
name: 'John Doe',
email: 'john@example.com'
});
// With interceptors
axios.interceptors.request.use(config => {
config.headers.Authorization = `Bearer ${token}`;
return config;
});React Query / TanStack Query
For advanced data fetching and caching:
npm install @tanstack/react-queryimport { useQuery, useMutation, QueryClient, QueryClientProvider } from '@tanstack/react-query';
const queryClient = new QueryClient();
function App() {
return (
<QueryClientProvider client={queryClient}>
<Users />
</QueryClientProvider>
);
}
function Users() {
const { data, isLoading, error } = useQuery({
queryKey: ['users'],
queryFn: () => fetch('https://api.example.com/users').then(res => res.json())
});
const mutation = useMutation({
mutationFn: (newUser) => {
return fetch('https://api.example.com/users', {
method: 'POST',
body: JSON.stringify(newUser),
headers: { 'Content-Type': 'application/json' }
});
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['users'] });
}
});
if (isLoading) return <div>Loading...</div>;
if (error) return <div>Error: {error.message}</div>;
return (
<div>
{data.map(user => <div key={user.id}>{user.name}</div>)}
<button onClick={() => mutation.mutate({ name: 'New User' })}>
Add User
</button>
</div>
);
}SWR
npm install swrimport useSWR from 'swr';
const fetcher = url => fetch(url).then(res => res.json());
function Profile() {
const { data, error, isLoading } = useSWR('/api/user', fetcher);
if (isLoading) return <div>Loading...</div>;
if (error) return <div>Failed to load</div>;
return <div>Hello {data.name}!</div>;
}Working with WebSockets
WebSockets are fully supported for real-time communication:
const socket = new WebSocket('wss://example.com/socket');
socket.addEventListener('open', (event) => {
console.log('Connected to server');
socket.send('Hello Server!');
});
socket.addEventListener('message', (event) => {
console.log('Message from server:', event.data);
});
socket.addEventListener('error', (event) => {
console.error('WebSocket error:', event);
});
socket.addEventListener('close', (event) => {
console.log('Disconnected from server');
});
// Send data
socket.send(JSON.stringify({ type: 'message', content: 'Hello' }));
// Close connection
socket.close();Local Data Storage
WebF provides standard Web Storage APIs for persisting data locally on the device.
localStorage
Use localStorage to store simple key-value pairs that persist even after the application is closed and reopened.
// Save data
localStorage.setItem('username', 'JohnDoe');
localStorage.setItem('settings', JSON.stringify({ theme: 'dark', fontSize: 14 }));
// Retrieve data
const username = localStorage.getItem('username'); // "JohnDoe"
const settings = JSON.parse(localStorage.getItem('settings'));
// Remove item
localStorage.removeItem('username');
// Clear all data
localStorage.clear();
// Check if key exists
if (localStorage.getItem('username') !== null) {
console.log('Username exists');
}
// Get number of items
console.log(localStorage.length);
// Iterate through items
for (let i = 0; i < localStorage.length; i++) {
const key = localStorage.key(i);
const value = localStorage.getItem(key);
console.log(`${key}: ${value}`);
}sessionStorage
Use sessionStorage for data that should only be available for the current session. This data is cleared when the application is completely closed.
// Save session data
sessionStorage.setItem('sessionId', 'abc123');
sessionStorage.setItem('tempData', JSON.stringify({ items: [1, 2, 3] }));
// Retrieve session data
const sessionId = sessionStorage.getItem('sessionId');
const tempData = JSON.parse(sessionStorage.getItem('tempData'));
// Clear session data
sessionStorage.clear();Storage Best Practices
// Helper functions for type-safe storage
const storage = {
set(key, value) {
try {
localStorage.setItem(key, JSON.stringify(value));
return true;
} catch (error) {
console.error('Storage error:', error);
return false;
}
},
get(key, defaultValue = null) {
try {
const item = localStorage.getItem(key);
return item ? JSON.parse(item) : defaultValue;
} catch (error) {
console.error('Storage error:', error);
return defaultValue;
}
},
remove(key) {
localStorage.removeItem(key);
},
clear() {
localStorage.clear();
}
};
// Usage
storage.set('user', { id: 1, name: 'John' });
const user = storage.get('user');React Hook for localStorage
import { useState, useEffect } from 'react';
function useLocalStorage(key, initialValue) {
const [storedValue, setStoredValue] = useState(() => {
try {
const item = window.localStorage.getItem(key);
return item ? JSON.parse(item) : initialValue;
} catch (error) {
console.error(error);
return initialValue;
}
});
const setValue = (value) => {
try {
const valueToStore = value instanceof Function ? value(storedValue) : value;
setStoredValue(valueToStore);
window.localStorage.setItem(key, JSON.stringify(valueToStore));
} catch (error) {
console.error(error);
}
};
return [storedValue, setValue];
}
// Usage
function App() {
const [name, setName] = useLocalStorage('name', 'Guest');
return (
<div>
<input value={name} onChange={(e) => setName(e.target.value)} />
<p>Hello, {name}!</p>
</div>
);
}Complex Data Storage
Important: IndexedDB is not supported in WebF.
For more complex data storage needs, such as managing a local database with queries, relationships, or large datasets, the recommended approach is to use a dedicated native plugin that can interface with a native database solution:
- SQLite: Via a custom native plugin
- Hive: Flutter’s native key-value database
- Isar: High-performance Flutter database
See the Build Native Plugins guide to learn how to expose native database functionality to your WebF app.
Error Handling
Always implement proper error handling for network requests:
async function fetchWithErrorHandling(url) {
try {
const response = await fetch(url);
// Check if response is ok (status 200-299)
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
// Check content type
const contentType = response.headers.get('content-type');
if (!contentType || !contentType.includes('application/json')) {
throw new TypeError('Response was not JSON');
}
const data = await response.json();
return { success: true, data };
} catch (error) {
if (error instanceof TypeError) {
// Network error or CORS issue
return { success: false, error: 'Network error. Please check your connection.' };
} else {
// HTTP error
return { success: false, error: error.message };
}
}
}Request Timeouts
Implement timeouts to prevent hanging requests:
async function fetchWithTimeout(url, timeout = 5000) {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), timeout);
try {
const response = await fetch(url, {
signal: controller.signal
});
clearTimeout(timeoutId);
return await response.json();
} catch (error) {
if (error.name === 'AbortError') {
throw new Error('Request timeout');
}
throw error;
}
}Next Steps
- Learn about State Management for managing application state
- Explore Native Plugins for accessing device features
- Check Security best practices for network requests