Fetch API#
OneJS provides a fetch function for HTTP requests.
Basic GET Request#
async function loadData() {
const response = await fetch("https://api.example.com/data")
const data = await response.json()
console.log(data)
}POST Request#
async function submitScore(score) {
const response = await fetch("https://api.example.com/scores", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ score }),
})
if (response.ok) {
console.log("Score submitted!")
}
}With React#
interface User {
name: string
email: string
}
function UserProfile({ userId }) {
const [user, setUser] = useState<User | null>(null)
const [loading, setLoading] = useState(true)
const [error, setError] = useState<string | null>(null)
useEffect(() => {
async function loadUser() {
try {
const res = await fetch(`https://api.example.com/users/${userId}`)
if (!res.ok) throw new Error("Failed to load user")
const data = await res.json()
setUser(data)
} catch (e) {
setError(e instanceof Error ? e.message : String(e))
} finally {
setLoading(false)
}
}
loadUser()
}, [userId])
if (loading) return <Label text="Loading..." />
if (error || !user) return <Label text={`Error: ${error}`} />
return (
<View>
<Label text={user.name} style={{ fontSize: 24 }} />
<Label text={user.email} style={{ color: "#999" }} />
</View>
)
}Request Options#
const response = await fetch(url, {
method: "POST", // GET, POST, PUT, DELETE or HEAD (see Notes)
headers: {
"Content-Type": "application/json",
"Authorization": "Bearer token123",
},
body: JSON.stringify(data),
})Header Formats#
Headers accept a plain object, a Headers instance, or an array of [name, value] pairs:
await fetch(url, { headers: { apikey: key } })
await fetch(url, { headers: new Headers({ apikey: key }) })
await fetch(url, { headers: [["apikey", key]] })All three forms work on every platform. This matters for third-party libraries (supabase-js, for example) that construct a Headers instance internally before calling fetch.
Response Methods#
const response = await fetch(url)
// Get JSON
const json = await response.json()
// Get text
const text = await response.text()
// Check status
if (response.ok) {
// 200-299
}
console.log(response.status) // 200
console.log(response.statusText) // "OK"Error Handling#
fetch resolves whenever a response arrives, whatever its status, so check response.ok for HTTP errors. When no response arrives at all (the network is down, the host does not resolve, the connection is refused), it rejects with a TypeError, as a browser does. Handle both in one try/catch:
async function fetchWithRetry(url, retries = 3) {
for (let i = 0; i < retries; i++) {
try {
const response = await fetch(url)
if (!response.ok) {
throw new Error(`HTTP ${response.status}`)
}
return await response.json()
} catch (error) {
if (i === retries - 1) throw error
await new Promise<void>((r) => setTimeout(r, 1000 * (i + 1)))
}
}
}Loading States Hook#
function useFetch<T>(url: string) {
const [data, setData] = useState<T | null>(null)
const [loading, setLoading] = useState(true)
const [error, setError] = useState<unknown>(null)
useEffect(() => {
let cancelled = false
async function load() {
try {
setLoading(true)
const res = await fetch(url)
const json = await res.json()
if (!cancelled) setData(json)
} catch (e) {
if (!cancelled) setError(e)
} finally {
if (!cancelled) setLoading(false)
}
}
load()
return () => { cancelled = true }
}, [url])
return { data, loading, error }
}
// Usage
function LeaderboardScreen() {
const { data, loading, error } = useFetch<{ name: string; score: number }[]>("/api/leaderboard")
if (loading) return <Label text="Loading..." />
if (error || !data) return <Label text="Failed to load" />
return (
<ScrollView>
{data.map((entry, i) => (
<Label key={i} text={`${i + 1}. ${entry.name}: ${entry.score}`} />
))}
</ScrollView>
)
}Polling#
function usePolling(url, interval = 5000) {
const [data, setData] = useState(null)
useEffect(() => {
async function poll() {
const res = await fetch(url)
const json = await res.json()
setData(json)
}
poll() // Initial fetch
const id = setInterval(poll, interval)
return () => clearInterval(id)
}, [url, interval])
return data
}Notes#
- On desktop and mobile it runs on Unity's
UnityWebRequest, which supports GET, POST, PUT, DELETE and HEAD (other methods, PATCH included, are sent as GET), and a network failure resolves withok: falseandstatus0 rather than rejecting. On WebGL it is the browser's ownfetch. - Works in Editor, Standalone, and WebGL builds
- CORS restrictions may apply in WebGL
- For local development, ensure your API server allows cross-origin requests