A super-fast network fetching library for React Native with prefetching support
> /plugin marketplace add margelo/react-native-nitro-fetch> /plugin install nitro-fetch@react-native-nitro-fetch
Repo: margelo/react-native-nitro-fetch
What's inside

react-native-nitro-fetch is a general purpose network fetching library for React Native. It can be used as a drop-in replacement for the built-in fetch(...) method, as well as provide additional features like prefetching and workletized mappers.
fetch(...) methodreact-native-nitro-websockets (see WebSockets & prewarm below)npm i react-native-nitro-fetch react-native-nitro-modules
Nitro Modules requires react-native 0.75+ or higher
WebSockets (optional) — add the companion socket package plus text decoder (peer dependency of websockets):
npm i react-native-nitro-websockets react-native-nitro-text-decoder
Full setup, native hooks, prewarm, and API details: docs/websockets.md · UI: example/src/screens/WebSocketScreen.tsx · auth + prewarm: Token refresh (example block).
To simply fetch data, import the fetch(...) method from react-native-nitro-fetch:
import { fetch } from 'react-native-nitro-fetch'
const res = await fetch('https://httpbin.org/get')
const json = await res.json()
This can be used as a drop-in-replacement for the built-in fetch(...) method.
You can prefetch a URL in JS, which keeps the result cached for the next actual fetch(...) call - this can be used shortly before navigating to a new screen to have results hot & ready:
import { prefetch } from 'react-native-nitro-fetch'
await prefetch('https://httpbin.org/uuid', {
headers: { prefetchKey: 'uuid' }
})
Then, on the new screen that was navigated to:
import { fetch } from 'react-native-nitro-fetch'
const res = await fetch('https://httpbin.org/uuid', {
headers: { prefetchKey: 'uuid' }
})
console.log('prefetched header:', res.headers.get('nitroPrefetched'))
Prefetching data on app launch (or process start) will make it hot & ready once your JS code actually runs. Call prefetchOnAppStart(...) to enqueue a prefetch for the next app start:
import { prefetchOnAppStart } from 'react-native-nitro-fetch'
await prefetchOnAppStart('https://httpbin.org/uuid', {
prefetchKey: 'uuid'
})
Then, once the app opens the next time, a call to fetch(...) might resolve faster since it will contain already cached results:
import { fetch } from 'react-native-nitro-fetch'
const res = await fetch('https://httpbin.org/uuid', {
headers: { prefetchKey: 'uuid' }
})
console.log('prefetched header:', res.headers.get('nitroPrefetched'))
In our tests, prefetching alone yielded a ~220 ms faster TTI (time-to-interactive) time! 🤯
When you use auto-prefetch (prefetchOnAppStart) and/or WebSocket prewarm on app start (react-native-nitro-websockets), native code runs before your JS bundle. If those requests need auth headers, you can register a token refresh configuration. On each cold start, native code calls your refresh URL, maps the response into HTTP headers, and merges them into auto-prefetches and/or WebSocket prewarms.
1. Register the refresh config (persisted in encrypted native storage):
import { registerTokenRefresh } from 'react-native-nitro-fetch'
registerTokenRefresh({
target: 'fetch', // 'websocket' | 'fetch' | 'all'
url: 'https://api.example.com/oauth/token',
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ grant_type: 'client_credentials' }),
responseType: 'json',
mappings: [
{ jsonPath: 'access_token', header: 'Authorization', valueTemplate: 'Bearer {{value}}' },
],
// If the refresh request fails:
// - 'useStoredHeaders' — use last successful headers from the previous run (default)
// - 'skip' — skip auto-prefetch / prewarm entirely when refresh fails
onFailure: 'useStoredHeaders',
})
Response mapping
responseType is 'json'. Use mappings to copy fields from the JSON body into header names (dot paths supported, e.g. data.token).compositeHeaders to build a header from a template and multiple JSON paths ({{placeholder}} in the template).responseType: 'text' and use textHeader / optional textTemplate (with {{value}}).fetch path only), use bodyMappings / formDataMappings (see below).Example: header + JSON body + form-data mappings
One config can fan the same refreshed value into all three destinations. Each mapping only applies to a prefetch whose body it matches — bodyMappings won't synthesize a JSON body on a GET/form request, and formDataMappings won't make a request multipart.
import { registerTokenRefresh } from 'react-native-nitro-fetch'
// Refresh endpoint returns e.g. { "data": { "accessToken": "abc123" } }
registerTokenRefresh({
target: 'fetch',
url: 'https://api.example.com/oauth/token',
method: 'POST',
responseType: 'json',
// 1) header mapping → sets a request header
mappings: [
{ jsonPath: 'data.accessToken', header: 'Authorization', valueTemplate: 'Bearer {{value}}' },
],
// 2) JSON-body mapping → sets a (nested) dot-path key in the prefetch's JSON body
bodyMappings: [
{ jsonPath: 'data.accessToken', bodyPath: 'auth.token' },
],
// 3) form-data mapping → replaces/appends a multipart field by name
formDataMappings: [
{ jsonPath: 'data.accessToken', field: 'token' },
],
onFailure: 'useStoredHeaders',
})
Result, per prefetch:
{ "deviceId": "d-1" } → Authorization: Bearer abc123 and body { "deviceId": "d-1", "auth": { "token": "abc123" } }Authorization: Bearer abc123 and a token=abc123 partFor
responseType: 'text', the body/form equivalents oftextHeaderarebodyTextPath/formDataTextField.
Example: token refresh + WebSocket prewarm
import { registerTokenRefresh } from 'react-native-nitro-fetch'
import { prewarmOnAppStart, NitroWebSocket } from 'react-native-nitro-websockets'
const WSS = 'wss://api.example.com/live'
registerTokenRefresh({
target: 'websocket', // use 'all' if you also use prefetchOnAppStart with the same token flow
url: 'https://api.example.com/oauth/token',
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
grant_type: 'client_credentials',
client_id: '…',
client_secret: '…',
}),
mappings: [
{ jsonPath: 'access_token', header: 'Authorization', valueTemplate: 'Bearer {{value}}' },
],
})
3. Optional JS helpers
import {
callRefreshEndpoint,
clearTokenRefresh,
getStoredTokenRefreshConfig,
} from 'react-native-nitro-fetch'
// Same mapping rules as native; uses global fetch from JS
const headers = await callRefreshEndpoint(config)
// Remove stored config and token caches (scope with 'fetch' | 'websocket' | 'all')
clearTokenRefresh('fetch')
// Read back what was registered (or null)
const stored = getStoredTokenRefreshConfig('fetch')
The refresh config and header caches are stored with platform secure storage (Android Keystore + encrypted values in SharedPreferences, iOS Keychain-backed encryption in the same UserDefaults suite as other nitro keys).
Cancel in-flight requests using the standard AbortController API:
import { fetch } from 'react-native-nitro-fetch'
const controller = new AbortController()
// Abort after 500ms
setTimeout(() => controller.abort(), 500)
try {
const res = await fetch('https://httpbin.org/delay/20', {
signal: controller.signal,
})
} catch (e) {
if (e.name === 'AbortError') {
console.log('Request was cancelled')
}
}
Pre-aborted signals are also supported — the request will throw immediately without making a network call:
const controller = new AbortController()
controller.abort()
await fetch(url, { signal: controller.signal }) // throws AbortError
Upload files and form fields using FormData:
import { fetch } from 'react-native-nitro-fetch'
const fd = new FormData()
fd.append('username', 'nitro_user')
fd.append('avatar', {
uri: 'file:///path/to/photo.jpg',
type: 'image/jpeg',
name: 'avatar.jpg',
} as any)
const res = await fetch('https://httpbin.org/post', {
method: 'POST',
body: fd,
})
const json = await res.json()
Since Nitro Fetch is a Nitro Module, it can be used from Worklets. This can be useful to parse data without blocking the main JS-Thread:
import { nitroFetchOnWorklet } from 'react-native-nitro-fetch'
const data = await nitroFetchOnWorklet(
'https://httpbin.org/get',
undefined,
(payload) => {
'worklet'
return JSON.parse(payload.bodyString ?? '{}')
}
)
Before using worklet mapping, install and configure react-native-worklets.
TextDecoderNitro Fetch can also expose an streaming mode that returns a ReadableStream body.
Combined with react-native-nitro-text-decoder, you can incrementally decode UTF‑8 chunks:
import { useRef, useState } from 'react'
import { fetch as nitroFetch } from 'react-native-nitro-fetch'
import { TextDecoder } from 'react-native-nitro-text-decoder'
export function StreamingExample() {
const [output, setOutput] = useState('')
const decoder = useRef(new TextDecoder())
const append = (text: string) => {
setOutput(prev => prev + text)
}
const runStream = async () => {
// `stream: true` enables the streaming transport
const res = await nitroFetch('https://httpbin.org/stream/20', {
stream: true,
})
const reader = res.body?.getReader()
if (!reader) {
append('No readable stream!')
return
}
let chunks = 0
while (true) {
const { done, value } = await reader.read()
if (done) break
chunks++
const text = decoder.current.decode(value, { stream: true })
append(text)
}
append(`\n\n✅ Done — ${chunks} chunk(s) received`)
}
// Call `runStream()` from a button handler in your UI
}
FAQ
react-native-nitro-fetch is a Claude Code plugin with 1 hand-picked skill for mobile work, indexed on Flowy. Install it with the command on its page. It includes nitro-fetch. Its skills do not fire on their own yet. Request auto-invocation to have Flowy route them as you prompt. Free and open source.
Is this plugin yours?
Claim it with GitHubSubmit a pluginPromote it