Skip to content

Repository files navigation

react-web-serial

React hooks and context provider for the Web Serial API.

Live demo: React Web Serial Monitor (Chromium-based browsers, a USB serial device such as an Arduino required)

Install

npm install react-web-serial

Usage

Examples

  • Serial Monitor — Minimal serial monitor app with an Arduino echo sketch

Basic

import { SerialProvider, useSerialPort } from "react-web-serial";

const SerialMonitor = () => {
  const {
    isSerialSupported,
    status,
    isConnected,
    isReading,
    receivedData,
    error,
    connect,
    disconnect,
    write,
    startReading,
    stopReading,
    clearError,
    clearReceivedData,
  } = useSerialPort({
    options: { baudRate: 9600 },
  });

  if (!isSerialSupported) {
    return <p>Web Serial API is not supported in this browser.</p>;
  }

  const connectAndRead = async () => {
    if (await connect()) startReading();
  };

  return (
    <div>
      <p>status: {status}</p>
      <button type="button" onClick={connectAndRead} disabled={status !== "idle"}>
        Connect
      </button>
      <button type="button" onClick={disconnect} disabled={!isConnected}>
        Disconnect
      </button>
      <button type="button" onClick={() => startReading()} disabled={!isConnected || isReading}>
        Start Reading
      </button>
      <button type="button" onClick={stopReading} disabled={!isReading}>
        Stop Reading
      </button>
      <button type="button" onClick={() => write("hello")} disabled={!isConnected}>
        Send "hello"
      </button>
      <button type="button" onClick={clearReceivedData}>Clear</button>

      {error && (
        <p>
          Error: {error.message} <button type="button" onClick={clearError}>Dismiss</button>
        </p>
      )}

      <ul>
        {receivedData.map((entry) => (
          <li key={entry.id}>
            {entry.mode === "text" ? entry.value : Array.from(entry.value).join(", ")}
          </li>
        ))}
      </ul>
    </div>
  );
};

const App = () => (
  <SerialProvider>
    <SerialMonitor />
  </SerialProvider>
);

Reconnecting without the chooser

navigator.serial.requestPort() must be called from a user gesture. To reconnect to a device the user has already granted access to (for example on page load), pass a port from getPorts():

const { connect, getPorts } = useSerialPort({ options: { baudRate: 9600 } });

useEffect(() => {
  getPorts().then(([port]) => {
    if (port) connect({ port });
  });
}, [connect, getPorts]);

Keeping connection-only components out of the data updates

While reading, receivedData is republished up to about 60 times per second, and every component that calls useSerialPort re-renders with it. Components that only need the connection (a toolbar, a status line) can use useSerialConnection instead; components that show the data use useSerialData. Both read the same store snapshot, so they never disagree.

const Toolbar = () => {
  const { status, connect, disconnect } = useSerialConnection({ options: { baudRate: 9600 } });
  // not re-rendered when data arrives
  ...
};

const Log = () => {
  const { receivedData, clearReceivedData } = useSerialData();
  ...
};

API

<SerialProvider>

Context provider. Wrap your app or component tree with this. The serial state lives in a store outside React that the provider observes with useSyncExternalStore.

<SerialProvider>{children}</SerialProvider>

useSerialPort(params?)

Returns the connection state and actions together with the received data (everything listed under State and Actions below). Re-renders on every change, including data flushes.

useSerialConnection(params?)

Same parameters as useSerialPort. Returns the State and Actions below except receivedData and clearReceivedData. Not re-rendered when receivedData changes.

useSerialData()

Returns { receivedData, clearReceivedData }.

Parameters

All parameters are optional defaults. An argument passed to connect() or startReading() overrides them field by field.

Name Type Description
options SerialOptions Options passed to port.open() (e.g. { baudRate: 9600 }). Required by connect(), here or as its argument
requestOptions SerialPortRequestOptions Filters for navigator.serial.requestPort()
maxReceivedDataCount number Max entries in receivedData buffer. Default: 1000
mode "text" | "binary" Data mode for startReading. Default: "text"

State

Name Type Description
isSerialSupported boolean Whether the browser supports Web Serial API
status "idle" | "connecting" | "connected" | "disconnecting" Connection status
isConnecting boolean status === "connecting"
isConnected boolean status === "connected"
port SerialPortInfo | null Connected port (exposes getInfo and forget)
isUserCancelled boolean User dismissed the port selection dialog on the last connect()
isReading boolean A read loop is running
receivedData SerialReceivedDataEntry[] Received data buffer (useSerialPort / useSerialData)
error Error | null Last error, kept for display until cleared or the next successful action

Actions

Every action reports its outcome through its return value; error additionally keeps the last failure for display.

Name Type Description
connect (params?: SerialConnectParams) => Promise<boolean> Open the chooser (or use params.port) and open the port. Resolves true when connected
disconnect () => Promise<boolean> Close the port. Resolves false only when close() failed; the port is kept so the call can be retried
write (data: string | Uint8Array) => Promise<boolean> Write data to the port. Writes are serialized
startReading (options?: StartReadingOptions) => boolean Start a read loop. Returns false if it could not start
stopReading () => Promise<void> Stop the read loop and release the reader
getPorts () => Promise<SerialPort[]> Ports the user has already granted access to
clearError () => void Clear error
clearReceivedData () => void Clear the received data buffer (useSerialPort / useSerialData)

Types

type SerialDataMode = "text" | "binary";

// `id` increases per entry and is never reused, so it works as a React key
// (several chunks can share one timestamp). It is unique within one
// SerialProvider; combine it with a port name if you merge lists from
// several providers.
type SerialReceivedDataEntry =
  | { id: number; mode: "text"; timestamp: Date; value: string }
  | { id: number; mode: "binary"; timestamp: Date; value: Uint8Array };

interface SerialConnectParams {
  options?: SerialOptions;
  requestOptions?: SerialPortRequestOptions;
  port?: SerialPort; // from getPorts(); skips the chooser
}

interface StartReadingOptions {
  maxReceivedDataCount?: number;
  mode?: SerialDataMode;
  onChunk?: (chunk: Uint8Array) => void; // raw chunks, before decoding and buffering
}

// Errors raised by the library itself; browser errors (DOMException) pass through.
class SerialError extends Error {
  code:
    | "not-available"
    | "options-required"
    | "invalid-state"
    | "not-connected"
    | "not-writable"
    | "already-reading";
}

Behavior notes

  • Serial state lives outside React in a store that the provider observes with useSyncExternalStore. Re-renders and StrictMode's double effects never open, close or re-lock the port.
  • Received chunks are published to receivedData at most once per ~16 ms (roughly one frame), so a chatty device does not cause a render per chunk. receivedData entries are chunks as delivered by the port, not lines; use onChunk for your own framing.
  • connect() while already connecting joins the in-flight attempt; while connected or disconnecting it records an invalid-state error.
  • When the device is removed (the port fires disconnect), status returns to "idle" and port is reset. A read error that preceded the removal is kept in error.
  • disconnect() releases the reader and waits for queued writes before calling port.close(), which rejects while a stream is locked. If close() fails, the port stays connected so disconnect() can be retried.

Browser support

Web Serial API is available in Chromium-based browsers (Chrome, Edge, Opera). See Can I use for details.

License

MIT

About

React hooks and context provider for the Web Serial API.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages