Skip to content

React Native

atmosphere.js 5.0.22 supports React Native and Expo via the atmosphere.js/react-native subpath export. Tested baseline: Expo SDK 55, React Native 0.83, React 19.2 (see samples/spring-boot-ai-classroom/expo-client/package.json).

Terminal window
bun add atmosphere.js
# Optional: for network-aware reconnection
bun add @react-native-community/netinfo
App.tsx
import NetInfo from '@react-native-community/netinfo';
import { setupReactNative, AtmosphereProvider, useAtmosphereRN } from 'atmosphere.js/react-native';
// Call once at app startup — pass NetInfo for network-aware reconnection
setupReactNative({ netInfo: NetInfo });
function Chat() {
const { data, state, push, isConnected } = useAtmosphereRN({
request: {
url: 'https://your-server.com/atmosphere/chat',
transport: 'websocket',
fallbackTransport: 'long-polling',
},
});
return (
// your UI
);
}
export default function App() {
return (
<AtmosphereProvider config={{ logLevel: 'info' }}>
<Chat />
</AtmosphereProvider>
);
}

Call this once before any Atmosphere subscriptions are created. It:

  • Installs a fetch-based EventSource polyfill (if the native one is missing)
  • Detects ReadableStream support (Hermes on RN 0.73+ / Expo SDK 50+)
  • Returns a capability report
import NetInfo from '@react-native-community/netinfo';
const caps = setupReactNative({ netInfo: NetInfo });
// { hasReadableStream: true, hasWebSocket: true, recommendedTransports: ['websocket', 'streaming', 'long-polling'] }

Without NetInfo:

const caps = setupReactNative();
// Works fine — isConnected defaults to true, network-aware reconnection is disabled

Returns true once setupReactNative() has run. Useful in tests and when a library wants to guard against double initialization.

import { isReactNativeSetup, setupReactNative } from 'atmosphere.js/react-native';
if (!isReactNativeSetup()) {
setupReactNative({ netInfo: NetInfo });
}

React Native has no window.location. All Atmosphere request URLs must be absolute:

// Good
{ url: 'https://example.com/atmosphere/chat', transport: 'websocket' }
// Bad - will throw an error in RN
{ url: '/atmosphere/chat', transport: 'websocket' }

Drop-in replacement for useAtmosphere with React Native lifecycle integration.

const { data, state, push, isConnected, isInternetReachable } = useAtmosphereRN<Message>({
request: { url: 'https://example.com/chat', transport: 'websocket' },
backgroundBehavior: 'suspend', // 'suspend' | 'disconnect' | 'keep-alive'
});

Background behavior options:

  • suspend (default) — pauses the transport when app goes to background, resumes on foreground
  • disconnect — fully closes the connection, reconnects on foreground
  • keep-alive — does nothing, connection stays open

NetInfo integration (when passed to setupReactNative({ netInfo: NetInfo })):

  • isConnected / isInternetReachable reflect real network state
  • Connection is suspended when offline, resumed when back online
  • Falls back to { isConnected: true, isInternetReachable: true } when NetInfo is not provided

Drop-in replacement for useStreaming with the same RN lifecycle awareness.

const { fullText, isStreaming, isConnected, send, reset, close } = useStreamingRN({
request: { url: 'https://example.com/ai/chat', transport: 'websocket' },
});

Sends are suppressed when the device is offline.

Shared low-level hook exported from atmosphere.js/react-native for building custom hooks. It manages subscription lifecycle, state, and cleanup — the RN-aware useAtmosphereRN and useStreamingRN hooks are built on top of it. Use it when the built-in hooks don’t fit your use case.

import { useAtmosphereCore } from 'atmosphere.js/react-native';
function useMyCustomHook(request) {
const { state, push, atmosphere } = useAtmosphereCore({
request,
handlers: {
onOpen: () => {},
onMessage: (data) => {},
},
});
// ...
}

These work as-is in React Native and are re-exported for convenience:

  • AtmosphereProvider / useAtmosphereContext
  • useRoom
  • usePresence
TransportExpo SDK 55 / RN 0.83 (tested)Expo SDK 50+ / RN 0.73+RN < 0.73Notes
WebSocketFull supportFull supportFull supportPrimary transport
Long-PollingFull supportFull supportFull supportSafe fallback (fetch only)
SSENative via polyfillVia polyfill (streaming)Via polyfill (text fallback)Polyfill degrades to polling on old Hermes
StreamingReadableStream worksReadableStream worksresponse.body is nullSkip on old Hermes

setupReactNative() detects capabilities and reports recommended transports.

When the app lives in a monorepo (e.g. samples/spring-boot-ai-classroom/expo-client/ with a file: dependency on the atmosphere.js sources) Metro needs a few tweaks so it can resolve the shared workspace without duplicating React:

metro.config.js
const { getDefaultConfig } = require('expo/metro-config');
const path = require('node:path');
const config = getDefaultConfig(__dirname);
const workspaceRoot = path.resolve(__dirname, '../../..');
config.watchFolders = [workspaceRoot];
config.resolver.nodeModulesPaths = [
path.resolve(__dirname, 'node_modules'),
path.resolve(workspaceRoot, 'atmosphere.js/node_modules'),
];
config.resolver.extraNodeModules = new Proxy({}, {
get: (_, name) => path.join(__dirname, `node_modules/${name}`),
});
config.resolver.blockList = [
new RegExp(`${workspaceRoot}/atmosphere.js/node_modules/react/.*`),
];
config.resolver.unstable_enablePackageExports = true;
module.exports = config;

The RN subpath uses a top-level import { AppState } from 'react-native'. Do not reach into esbuild/tsup’s synthetic require() helper — Metro cannot statically resolve wrapped require() calls. If you vendor the source, keep the static top-level import.

@react-native-community/netinfo is not imported by atmosphere.js — you must pass it explicitly: setupReactNative({ netInfo: NetInfo }). This keeps NetInfo optional and avoids Metro static-analysis failures on optional dependencies.

Expo Go requires registerRootComponent(App) at the entry. If you forget, the bundler builds but Expo Go never renders:

index.ts
import { registerRootComponent } from 'expo';
import App from './App';
registerRootComponent(App);
  • Hermes + ReadableStream: Hermes added ReadableStream support in RN 0.73 (Expo SDK 50). On older versions, SSE and streaming transports degrade or are unavailable.
  • No window.location: All URLs must be absolute. The WebSocket transport throws a clear error if given a relative URL without window.location.
  • NetInfo is optional: Pass it via setupReactNative({ netInfo: NetInfo }). Without it, isConnected defaults to true and network-aware reconnection is disabled.
  • Background audio: If you need to keep a connection alive while the app plays audio in the background, use backgroundBehavior: 'keep-alive'.

See samples/spring-boot-ai-classroom/expo-client/ for a complete Expo app that connects to the AI Classroom backend with 4 rooms (Math, Code, Science, General), streaming AI responses, and network status display.