Docs/Guides/Quick Start — React Native
Guides

Quick start — React Native

End-to-end: from an API key to a live match in your Expo or bare React Native app — no SDK required.

@beta-gamer/react-native exists on npm but predates the current hosted-matchmaking architecture and isn't updated for it — installing it today won't give you a working integration. It's being rebuilt separately and will come back once it matches what's live. This guide — plain socket.io-client plus a small hook — is the real, working path right now, not a stopgap.
1

Get your API key

Same as the web flow — a bg_live_… key and a bg_test_… key. Use the test key while developing.

🔒 Never bundle your API key in the app. Session creation must happen on your backend — the device only ever sees the short-lived session token.
2

Install the one real dependency

bash
npm install socket.io-client
In React Native, force the websocket transport explicitly. XHR polling — Socket.IO's usual fallback transport — isn't reliably available in RN's environment, and silently falling back to it is a common source of "it works in the simulator, not on device" reports:
js
io(url, { auth: { token }, autoConnect: false, transports: ['websocket'] });
3

Create a session (your backend)

Call POST /v1/sessions from your server and return the sessionToken to the app. Never call this from the device — it needs your secret API key.

js
const res = await fetch('https://api.beta-gamer.com/v1/sessions', {
  method: 'POST',
  headers: {
    'Authorization': 'Bearer bg_live_xxxx',
    'Content-Type':  'application/json',
  },
  body: JSON.stringify({
    game:      'checkers',
    matchType: 'matchmaking',
    players:   [{ id: user.id, displayName: user.name }],
  }),
});
const { sessionToken, sessionId } = await res.json();
4

Fetch the token in your app

tsx
const startGame = async () => {
  const res = await fetch('https://your-api.com/game/start', {
    method: 'POST',
    headers: { 'Authorization': `Bearer ${userAuthToken}` },
  });
  const { sessionToken, sessionId } = await res.json();
  navigation.navigate('Game', { sessionToken, sessionId });
};
5

Write a small connection hook

Same hook as the web guide, with transports: ['websocket'] added:

tsx
// useBetaGamerSocket.ts
import { useEffect, useRef, useState } from 'react';
import { io, Socket } from 'socket.io-client';

export function useBetaGamerSocket(game: string, sessionToken: string | null) {
  const socketRef = useRef<Socket | null>(null);
  const [connected, setConnected] = useState(false);

  useEffect(() => {
    if (!sessionToken) return;

    const socket = io(`https://api.beta-gamer.com/${game}`, {
      auth: { token: sessionToken },
      autoConnect: false,
      transports: ['websocket'],
    });
    socketRef.current = socket;

    socket.on('connect', () => setConnected(true));
    socket.on('disconnect', () => setConnected(false));
    socket.connect();

    return () => {
      socket.disconnect();
      socketRef.current = null;
    };
  }, [game, sessionToken]);

  return { socket: socketRef.current, connected };
}
6

Join matchmaking, render from state

There's no ChessBoard-style pre-built component without the SDK — you own rendering. That's more upfront work, and also means your board looks exactly like your app, not like a generic default.

tsx
export default function GameScreen({ route }) {
  const { sessionToken } = route.params;
  const { socket, connected } = useBetaGamerSocket('checkers', sessionToken);
  const [board, setBoard] = useState(null);
  const [status, setStatus] = useState('Connecting…');

  useEffect(() => {
    if (!socket || !connected) return;
    socket.emit('matchmaking:join', { playerId: myId, username: myName });

    socket.on('game:started', (data) => {
      setBoard(data.board);
      setStatus(`Game started — you're ${data.color}`);
    });
    socket.on('game:move:made', (data) => setBoard(data.board));
    socket.on('game:over', ({ winner }) => {
      setStatus(winner === myId ? 'You won!' : winner ? 'You lost' : 'Draw');
    });

    return () => { socket.off('game:started'); socket.off('game:move:made'); socket.off('game:over'); };
  }, [socket, connected]);

  if (!board) return <ActivityIndicator />;
  return <YourOwnBoardComponent board={board} onCellPress={(from, to) =>
    socket.emit('game:move', { roomId, playerId: myId, move: { from, to } })
  } />;
}
7

Validate the token on mount

Tokens are single-use. Validate before rendering the game screen so navigating back to a finished match shows the result, not a socket trying to rejoin something that's already over.

tsx
useEffect(() => {
  fetch(`https://api.beta-gamer.com/v1/sessions/validate?token=${sessionToken}`)
    .then(r => r.json())
    .then(d => setStatus(d.status === 'ended' ? 'ended' : 'ok'))
    .catch(() => setStatus('ok'));
}, [sessionToken]);

A note on mobile lifecycle

ConcernNote
App backgrounded mid-gameThe socket disconnects like any background app. Reconnect on foreground using the same sessionToken and the per-game reconnect event — see each game’s own page.
Metro / dev server restartsBehaves like any other socket disconnect from the server’s point of view — no special handling needed beyond normal reconnection.
✅ A running match, end to end — see the Games catalog for your specific game's board shape and move format.