Skip to content

rolandvar/react-native-nitro-fetch

 
 

Repository files navigation

Nitro Modules

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.

Documentation

Features

Installation

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).

Usage

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.

Prefetching in JS

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 for the next app launch

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! 🤯

Token refresh (cold start)

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

  • Default responseType is 'json'. Use mappings to copy fields from the JSON body into header names (dot paths supported, e.g. data.token).
  • Use compositeHeaders to build a header from a template and multiple JSON paths ({{placeholder}} in the template).
  • For a plain-text body, set responseType: 'text' and use textHeader / optional textTemplate (with {{value}}).

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).

AbortController

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

FormData

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()

Worklet Mapping

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.

Streaming with TextDecoder

Nitro 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
}

WebSockets & prewarm

Use react-native-nitro-websockets for NitroWebSocket (browser-like API: onopen, onmessage, send, close, …). Install react-native-nitro-text-decoder alongside it — the socket package uses it to decode UTF-8 text frames.

Prewarm on next launch — queue URLs from JS so native code can start the handshake before React loads:

import {
  prewarmOnAppStart,
  removeFromPrewarmQueue,
  clearPrewarmQueue,
} from 'react-native-nitro-websockets'

prewarmOnAppStart('wss://echo.websocket.org')
// optional: prewarmOnAppStart(url, ['subproto'], { Authorization: 'Bearer …' })

clearPrewarmQueue()
removeFromPrewarmQueue('wss://echo.websocket.org')

On Android, call NitroWebSocketAutoPrewarmer.prewarmOnStart(this) in Application.onCreate (see example MainApplication.kt). iOS picks up the queue via the linked pod.

Authenticated prewarms: use registerTokenRefresh with target: 'websocket' or 'all' — see Token refresh (cold start) for a small registerTokenRefresh + prewarmOnAppStart + NitroWebSocket example.

More detail: docs/websockets.md · UI sample: example/src/screens/WebSocketScreen.tsx.

Limitations & Alternatives

Documentation

Margelo

Nitro Fetch is built with ❤️ by Margelo. We build fast and beautiful apps. Contact us at margelo.com for high-end consultancy services.

Contributing

  • Development workflow: CONTRIBUTING.md#development-workflow
  • Sending a pull request: CONTRIBUTING.md#sending-a-pull-request
  • Code of conduct: CODE_OF_CONDUCT.md

Authors

License

MIT

About

A super-fast network fetching library for React Native with prefetching support

Resources

License

Code of conduct

Contributing

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages

  • TypeScript 49.9%
  • Kotlin 13.6%
  • C++ 12.8%
  • Swift 9.1%
  • Objective-C++ 6.4%
  • CSS 3.2%
  • Other 5.0%