{"id":14987483,"url":"https://github.com/pmndrs/react-ogl","last_synced_at":"2025-05-16T07:03:58.457Z","repository":{"id":37840069,"uuid":"424583411","full_name":"pmndrs/react-ogl","owner":"pmndrs","description":"🦴 A barebones react renderer for ogl.","archived":false,"fork":false,"pushed_at":"2025-01-27T08:15:27.000Z","size":2405,"stargazers_count":205,"open_issues_count":1,"forks_count":8,"subscribers_count":8,"default_branch":"main","last_synced_at":"2025-05-09T05:27:56.908Z","etag":null,"topics":["ogl","react","renderer","shaders","webgl","webxr"],"latest_commit_sha":null,"homepage":"https://npmjs.com/react-ogl","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/pmndrs.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null},"funding":{"github":["CodyJasonBennett"]}},"created_at":"2021-11-04T12:15:09.000Z","updated_at":"2025-04-15T16:57:01.000Z","dependencies_parsed_at":"2024-04-07T01:52:36.475Z","dependency_job_id":"4870cd1c-b948-448c-9a53-c90cf560cf97","html_url":"https://github.com/pmndrs/react-ogl","commit_stats":{"total_commits":280,"total_committers":7,"mean_commits":40.0,"dds":"0.24642857142857144","last_synced_commit":"13702e5b43e9fb88db951782375aaf7567b424ee"},"previous_names":[],"tags_count":52,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pmndrs%2Freact-ogl","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pmndrs%2Freact-ogl/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pmndrs%2Freact-ogl/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pmndrs%2Freact-ogl/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/pmndrs","download_url":"https://codeload.github.com/pmndrs/react-ogl/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254485053,"owners_count":22078767,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["ogl","react","renderer","shaders","webgl","webxr"],"created_at":"2024-09-24T14:14:42.679Z","updated_at":"2025-05-16T07:03:58.436Z","avatar_url":"https://github.com/pmndrs.png","language":"TypeScript","funding_links":["https://github.com/sponsors/CodyJasonBennett"],"categories":[],"sub_categories":[],"readme":"[![Size](https://img.shields.io/bundlephobia/minzip/react-ogl?label=gzip\u0026style=flat\u0026colorA=000000\u0026colorB=000000)](https://bundlephobia.com/package/react-ogl)\n[![Version](https://img.shields.io/npm/v/react-ogl?style=flat\u0026colorA=000000\u0026colorB=000000)](https://npmjs.com/package/react-ogl)\n[![Downloads](https://img.shields.io/npm/dt/react-ogl.svg?style=flat\u0026colorA=000000\u0026colorB=000000)](https://npmjs.com/package/react-ogl)\n[![Twitter](https://img.shields.io/twitter/follow/pmndrs?label=%40pmndrs\u0026style=flat\u0026colorA=000000\u0026colorB=000000\u0026logo=twitter\u0026logoColor=000000)](https://twitter.com/pmndrs)\n[![Discord](https://img.shields.io/discord/740090768164651008?style=flat\u0026colorA=000000\u0026colorB=000000\u0026label=discord\u0026logo=discord\u0026logoColor=000000)](https://discord.gg/poimandres)\n\n\u003cp align=\"left\"\u003e\n  \u003ca id=\"cover\" href=\"#cover\"\u003e\n    \u003cpicture\u003e\n      \u003csource media=\"(prefers-color-scheme: dark)\" srcset=\".github/dark.svg\"\u003e\n      \u003cimg style=\"white-space:pre-wrap\" alt=\"Build OGL scenes declaratively with re-usable, self-contained components that react to state, are readily interactive and can participate in React's ecosystem.\u0026#10\u0026#10react-ogl is a barebones react renderer for OGL with an emphasis on minimalism and modularity. Its reconciler simply expresses JSX as OGL elements — \u003cmesh /\u003e becomes new OGL.Mesh(). This happens dynamically; there's no wrapper involved.\"\u003e\n    \u003c/picture\u003e\n  \u003c/a\u003e\n\u003c/p\u003e\n\n## Table of Contents\n\n- [Installation](#installation)\n- [Getting Started](#getting-started)\n  - [react-dom](#react-dom)\n  - [react-native](#react-native)\n- [Canvas](#canvas)\n  - [Canvas Props](#canvas-props)\n  - [Custom Canvas](#custom-canvas)\n- [Creating Elements](#creating-elements)\n  - [JSX, properties, and shortcuts](#jsx-properties-and-shortcuts)\n  - [Setting constructor arguments via `args`](#setting-constructor-arguments-via-args)\n  - [Attaching into element properties via `attach`](#attaching-into-element-properties-via-attach)\n  - [Creating custom elements via `extend`](#creating-custom-elements-via-extend)\n  - [Adding third-party objects via `\u003cprimitive /\u003e`](#adding-third-party-objects-via-primitive-)\n- [Hooks](#hooks)\n  - [Root State](#root-state)\n  - [Accessing state via `useOGL`](#accessing-state-via-useogl)\n  - [Frameloop subscriptions via `useFrame`](#frameloop-subscriptions-via-useframe)\n  - [Loading assets via `useLoader`](#loading-assets-via-useloader)\n  - [Object traversal via `useGraph`](#object-traversal-via-usegraph)\n  - [Transient updates via `useStore`](#transient-updates-via-usestore)\n  - [Access internals via `useInstanceHandle`](#access-internals-via-useinstancehandle)\n- [Events](#events)\n  - [Custom Events](#custom-events)\n- [Portals](#portals)\n- [Testing](#testing)\n\n## Installation\n\n```bash\n# NPM\nnpm install ogl react-ogl\n\n# Yarn\nyarn add ogl react-ogl\n\n# PNPM\npnpm add ogl react-ogl\n```\n\n## Getting Started\n\nreact-ogl itself is super minimal, but you can use the familiar [@react-three/fiber](https://github.com/pmndrs/react-three-fiber) API with some helpers targeted for different platforms:\n\n### react-dom\n\nThis example uses [`create-react-app`](https://reactjs.org/docs/create-a-new-react-app.html#create-react-app) for the sake of simplicity, but you can use your own environment or [create a codesandbox](https://react.new).\n\n\u003cdetails\u003e\n  \u003csummary\u003eShow full example\u003c/summary\u003e\n  \n  \u003cbr /\u003e\n\n```bash\n# Create app\nnpx create-react-app my-app\ncd my-app\n\n# Install dependencies\nnpm install ogl react-ogl\n\n# Start\nnpm run start\n```\n\nThe following creates a re-usable component that has its own state, reacts to events and participates a shared render-loop.\n\n```jsx\nimport * as React from 'react'\nimport { useFrame, Canvas } from 'react-ogl'\nimport { createRoot } from 'react-dom/client'\n\nfunction Box(props) {\n  // This reference will give us direct access to the mesh\n  const mesh = React.useRef()\n  // Set up state for the hovered and active state\n  const [hovered, setHover] = React.useState(false)\n  const [active, setActive] = React.useState(false)\n  // Subscribe this component to the render-loop, rotate the mesh every frame\n  useFrame(() =\u003e (mesh.current.rotation.x += 0.01))\n  // Return view, these are regular OGL elements expressed in JSX\n  return (\n    \u003cmesh\n      {...props}\n      ref={mesh}\n      scale={active ? 1.5 : 1}\n      onClick={() =\u003e setActive((value) =\u003e !value)}\n      onPointerOver={() =\u003e setHover(true)}\n      onPointerOut={() =\u003e setHover(false)}\n    \u003e\n      \u003cbox /\u003e\n      \u003cprogram\n        vertex={`\n          attribute vec3 position;\n          attribute vec3 normal;\n\n          uniform mat4 modelViewMatrix;\n          uniform mat4 projectionMatrix;\n          uniform mat3 normalMatrix;\n\n          varying vec3 vNormal;\n\n          void main() {\n            vNormal = normalize(normalMatrix * normal);\n            gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);\n          }\n        `}\n        fragment={`\n          precision highp float;\n\n          uniform vec3 uColor;\n          varying vec3 vNormal;\n\n          void main() {\n            vec3 normal = normalize(vNormal);\n            float lighting = dot(normal, normalize(vec3(10)));\n\n            gl_FragColor.rgb = uColor + lighting * 0.1;\n            gl_FragColor.a = 1.0;\n          }\n        `}\n        uniforms={{ uColor: hovered ? 'hotpink' : 'orange' }}\n      /\u003e\n    \u003c/mesh\u003e\n  )\n}\n\ncreateRoot(document.getElementById('root')).render(\n  \u003cCanvas camera={{ position: [0, 0, 8] }}\u003e\n    \u003cBox position={[-1.2, 0, 0]} /\u003e\n    \u003cBox position={[1.2, 0, 0]} /\u003e\n  \u003c/Canvas\u003e,\n)\n```\n\n\u003c/details\u003e\n\n### react-native\n\nThis example uses [`expo-cli`](https://docs.expo.dev/get-started/create-a-new-app) but you can create a bare app with `react-native` CLI as well.\n\n\u003cdetails\u003e\n  \u003csummary\u003eShow full example\u003c/summary\u003e\n  \n  \u003cbr /\u003e\n\n```bash\n# Create app and cd into it\nnpx expo init my-app # or npx react-native init my-app\ncd my-app\n\n# Automatically install \u0026 link expo modules\nnpx install-expo-modules@latest\nexpo install expo-gl\n\n# Install NPM dependencies\nnpm install ogl react-ogl\n\n# Start\nnpm run start\n```\n\nWe'll also need to configure `metro.config.js` to look for the mjs file extension that OGL uses.\n\n```js\nmodule.exports = {\n  resolver: {\n    resolverMainFields: ['browser', 'exports', 'main'], // https://github.com/facebook/metro/issues/670\n    sourceExts: ['json', 'js', 'jsx', 'ts', 'tsx', 'cjs', 'mjs'],\n    assetExts: ['glb', 'gltf', 'png', 'jpg'],\n  },\n}\n```\n\nInside of our app, you can use the same API as web while running on native OpenGL ES — no webview needed.\n\n```js\nimport * as React from 'react'\nimport { useFrame, Canvas } from 'react-ogl'\n\nfunction Box(props) {\n  // This reference will give us direct access to the mesh\n  const mesh = React.useRef()\n  // Set up state for the hovered and active state\n  const [hovered, setHover] = React.useState(false)\n  const [active, setActive] = React.useState(false)\n  // Subscribe this component to the render-loop, rotate the mesh every frame\n  useFrame(() =\u003e (mesh.current.rotation.x += 0.01))\n  // Return view, these are regular OGL elements expressed in JSX\n  return (\n    \u003cmesh\n      {...props}\n      ref={mesh}\n      scale={active ? 1.5 : 1}\n      onClick={() =\u003e setActive((value) =\u003e !value)}\n      onPointerOver={() =\u003e setHover(true)}\n      onPointerOut={() =\u003e setHover(false)}\n    \u003e\n      \u003cbox /\u003e\n      \u003cprogram\n        vertex={`\n          attribute vec3 position;\n          attribute vec3 normal;\n\n          uniform mat4 modelViewMatrix;\n          uniform mat4 projectionMatrix;\n          uniform mat3 normalMatrix;\n\n          varying vec3 vNormal;\n\n          void main() {\n            vNormal = normalize(normalMatrix * normal);\n            gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);\n          }\n        `}\n        fragment={`\n          precision highp float;\n\n          uniform vec3 uColor;\n          varying vec3 vNormal;\n\n          void main() {\n            vec3 normal = normalize(vNormal);\n            float lighting = dot(normal, normalize(vec3(10)));\n\n            gl_FragColor.rgb = uColor + lighting * 0.1;\n            gl_FragColor.a = 1.0;\n          }\n        `}\n        uniforms={{ uColor: hovered ? 'hotpink' : 'orange' }}\n      /\u003e\n    \u003c/mesh\u003e\n  )\n}\n\nexport default () =\u003e (\n  \u003cCanvas camera={{ position: [0, 0, 8] }}\u003e\n    \u003cBox position={[-1.2, 0, 0]} /\u003e\n    \u003cBox position={[1.2, 0, 0]} /\u003e\n  \u003c/Canvas\u003e\n)\n```\n\n\u003c/details\u003e\n\n## Canvas\n\nreact-ogl provides an x-platform `\u003cCanvas /\u003e` component for web and native that serves as the entrypoint for your OGL scenes. It is a real DOM canvas or native view that accepts OGL elements as children (see [creating elements](#creating-elements)).\n\n### Canvas Props\n\nIn addition to its platform props, `\u003cCanvas /\u003e` accepts a set of `RenderProps` to configure react-ogl and its rendering behavior.\n\n```tsx\n\u003cCanvas\n  // Configures the react rendering mode. Defaults to `blocking`\n  mode={\"legacy\" | \"blocking\" | \"concurrent\"}\n  // Creates, sets, or configures the default renderer.\n  // Accepts a callback, an external renderer, or renderer constructor params/properties.\n  // Defaults to `new OGL.Renderer({ alpha: true, antialias: true, powerPreference: 'high-performance' })\n  renderer={(canvas: HTMLCanvasElement) =\u003e new Renderer(canvas) | renderer | { ...params, ...props }}\n  // Sets the renderer pixel ratio from a clamped range or value. Default is `[1, 2]`\n  dpr={[min, max] | value}\n  // Sets or configures the default camera.\n  // Accepts an external camera, or camera constructor params/properties.\n  // Defaults to `new OGL.Camera(gl, { fov: 75, near: 1, far: 1000 })` with position-z `5`\n  camera={camera | { ...params, ...props }}\n  // Enables orthographic projection when using OGL's built-in camera. Default is `false`\n  orthographic={true | false}\n  // Defaults to `always`\n  frameloop={'always' | 'never'}\n  // An optional callback invoked after canvas creation and before commit.\n  onCreated={(state: RootState) =\u003e void}\n  // Optionally configures custom events. Defaults to built-in events exported as `events`\n  events={EventManager | undefined}\n\u003e\n  {/* Accepts OGL elements as children */}\n  \u003ctransform /\u003e\n\u003c/Canvas\u003e\n\n// e.g.\n\n\u003cCanvas\n  renderer={{ alpha: true }}\n  camera={{ fov: 45, position: [0, 1.3, 3] }}\n  onCreated={(state) =\u003e void state.gl.clearColor(1, 1, 1, 0)}\n\u003e\n  \u003ctransform /\u003e\n\u003c/Canvas\u003e\n```\n\n### Custom Canvas\n\nA react 18 style `createRoot` API creates an imperative `Root` with the same options as `\u003cCanvas /\u003e`, but you're responsible for updating it and configuring things like events (see [events](#events)). This root attaches to an `HTMLCanvasElement` and renders OGL elements into a scene. Useful for creating an entrypoint with react-ogl and for headless contexts like a server or testing (see [testing](#testing)).\n\n```jsx\nimport { createRoot, events } from 'react-ogl'\n\nconst canvas = document.querySelector('canvas')\nconst root = createRoot(canvas, { events })\nroot.render(\n  \u003cmesh\u003e\n    \u003cbox /\u003e\n    \u003cnormalProgram /\u003e\n  \u003c/mesh\u003e,\n)\nroot.unmount()\n```\n\n`createRoot` can also be used to create a custom `\u003cCanvas /\u003e`. The following constructs a custom canvas that renders its children into react-ogl.\n\n```jsx\nimport * as React from 'react'\nimport { createRoot, events } from 'react-ogl'\n\nfunction CustomCanvas({ children }) {\n  // Init root from canvas\n  const [canvas, setCanvas] = React.useState()\n  const root = React.useMemo(() =\u003e canvas \u0026\u0026 createRoot(canvas, { events }), [canvas])\n  // Render children as a render-effect\n  root?.render(children)\n  // Cleanup on unmount\n  React.useEffect(() =\u003e () =\u003e root?.unmount(), [root])\n  // Use callback-style ref to access canvas in render\n  return \u003ccanvas ref={setCanvas} /\u003e\n}\n```\n\n## Creating elements\n\nreact-ogl renders React components into an OGL scene-graph, and can be used on top of other renderers like [react-dom](https://npmjs.com/react-dom) and [react-native](https://npmjs.com/react-native) that render for web and native, respectively. react-ogl components are defined by primitives or lower-case elements native to the OGL namespace (for custom elements, see [extend](#creating-custom-elements-via-extend)).\n\n```jsx\nfunction Component(props) {\n  return (\n    \u003cmesh {...props}\u003e\n      \u003cbox /\u003e\n      \u003cnormalProgram /\u003e\n    \u003c/mesh\u003e\n  )\n}\n\n;\u003ctransform\u003e\n  \u003cComponent position={[1, 2, 3]} /\u003e\n\u003c/transform\u003e\n```\n\nThese elements are not exported or implemented internally, but merely expressed as JSX — `\u003cmesh /\u003e` becomes `new OGL.Mesh()`. This happens dynamically; there's no wrapper involved.\n\n### JSX, properties, and shortcuts\n\nreact-ogl elements can be modified with JSX attributes or props. These are native to their underlying OGL objects.\n\n```jsx\n\u003ctransform\n  // Set non-atomic properties with literals\n  // transform.visible = false\n  visible={false}\n  // Copy atomic properties with a stable reference (e.g. useMemo)\n  // transform.rotation.copy(rotation)\n  rotation={rotation}\n  // Set atomic properties with declarative array syntax\n  // transform.position.set(1, 2, 3)\n  position={[1, 2, 3]}\n  // Set scalars with shorthand for vector properties\n  // transform.scale.set(1, 1, 1)\n  scale={1}\n  // Set CSS names or hex values as shorthand for color properties\n  // transform.color.set('red')\n  color=\"red\"\n  // Set sub properties with prop piercing or dash-case\n  // transform.rotation.x = Math.PI / 2\n  rotation-x={Math.PI / 2}\n/\u003e\n```\n\n### Setting constructor arguments via `args`\n\nAn array of constructor arguments (`args`) can be passed to instantiate elements' underlying OGL objects. Changing `args` will reconstruct the object and update any associated refs.\n\n```jsx\n// new OGL.Text({ font, text: 'Text' })\n\u003ctext args={[{ font, text: 'Text' }]} /\u003e\n```\n\nBuilt-in elements that require a `gl` context such as `\u003cmesh /\u003e`, `\u003cgeometry /\u003e`, or `\u003cprogram /\u003e` are marked as effectful (see [extend](#creating-custom-elements-via-extend)) and do not require an `OGLRenderingContext` to be passed via `args`. They can be constructed mutably and manipulated via props:\n\n```jsx\n\u003cmesh\u003e\n  \u003cbox /\u003e\n  \u003cnormalProgram /\u003e\n\u003c/mesh\u003e\n```\n\n`\u003cgeometry /\u003e` and `\u003cprogram /\u003e` also accept attributes and shader sources as props, which are passed to their respective constructors. This does not affect other properties like `drawRange` or `uniforms`.\n\n```jsx\n\u003cmesh\u003e\n  \u003cgeometry\n    position={{ size: 3, data: new Float32Array([-0.5, 0.5, 0, -0.5, -0.5, 0, 0.5, 0.5, 0, 0.5, -0.5, 0]) }}\n    uv={{ size: 2, data: new Float32Array([0, 1, 1, 1, 0, 0, 1, 0]) }}\n    index={{ data: new Uint16Array([0, 1, 2, 1, 3, 2]) }}\n  /\u003e\n  {/* prettier-ignore */}\n  \u003cprogram\n    vertex={/* glsl */ `...`}\n    fragment={/* glsl */ `...`}\n    uniforms={{ uniform: value }}\n  /\u003e\n\u003c/mesh\u003e\n```\n\n### Attaching into element properties via `attach`\n\nSome elements do not follow the traditional scene-graph and need to be added by other means. For this, the `attach` prop can describe where an element is added via a property or a callback to add \u0026 remove the element.\n\n```jsx\n// Attaches into parent.property, parent.sub.property, and parent.array[0]\n\u003cparent\u003e\n  \u003celement attach=\"property\" /\u003e\n  \u003celement attach=\"sub-property\" /\u003e\n  \u003celement attach=\"array-0\" /\u003e\n\u003c/parent\u003e\n\n// Attaches via parent#setProperty and parent#removeProperty\n\u003cparent\u003e\n  \u003celement\n    attach={(parent, self) =\u003e {\n      parent.setProperty(self)\n      return () =\u003e parent.removeProperty(self)\n    }}\n    // lambda version\n    attach={(parent, self) =\u003e (parent.setProperty(self), () =\u003e parent.removeProperty(self))}\n  /\u003e\n\u003c/parent\u003e\n```\n\nElements who extend `OGL.Geometry` or `OGL.Program` will automatically attach via `attach=\"geometry\"` and `attach=\"program\"`, respectively.\n\n```jsx\n\u003cmesh\u003e\n  \u003cbox /\u003e\n  \u003cnormalProgram /\u003e\n\u003c/mesh\u003e\n```\n\n### Creating custom elements via `extend`\n\nreact-ogl tracks an internal catalog of constructable elements, defaulting to the OGL namespace. This catalog can be expanded via `extend` to declaratively use custom elements as native elements.\n\n```jsx\nimport { extend } from 'react-ogl'\n\nclass CustomElement {}\nextend({ CustomElement })\n\n\u003ccustomElement /\u003e\n```\n\nTypeScript users will need to extend the `OGLElements` interface to describe custom elements and their properties.\n\n```tsx\nimport { OGLElement, extend } from 'react-ogl'\n\nclass CustomElement {}\n\ndeclare module 'react-ogl' {\n  interface OGLElements {\n    customElement: OGLElement\u003ctypeof CustomElement\u003e\n  }\n}\n\nextend({ CustomElement })\n```\n\nEffectful elements that require a `gl` context can mark themselves as effectful and receive a `OGLRenderingContext` when constructed, making args mutable and enabling the use of props. This is done for OGL built-in elements like `\u003cmesh /\u003e`, `\u003cgeometry /\u003e`, and `\u003cprogram /\u003e`.\n\n```jsx\nimport { extend } from 'react-ogl'\n\nclass CustomElement {\n  constructor(gl) {\n    this.gl = gl\n  }\n}\nextend({ CustomElement }, true)\n\n\u003ccustomElement /\u003e\n```\n\n### Adding third-party objects via `\u003cprimitive /\u003e`\n\nObjects created outside of React (e.g. globally or from a loader) can be added to the scene-graph with the `\u003cprimitive /\u003e` element via its `object` prop. Primitives can be interacted with like any other element, but will modify `object` and cannot make use of `args`.\n\n```jsx\nimport * as OGL from 'ogl'\n\nconst object = new OGL.Transform()\n\n\u003cprimitive object={object} position={[1, 2, 3]} /\u003e\n```\n\n## Hooks\n\nreact-ogl ships with hooks that allow you to tie or request information to your components. These are called within the body of `\u003cCanvas /\u003e` and contain imperative and possibly stateful code.\n\n### Root State\n\nEach `\u003cCanvas /\u003e` or `Root` encapsulates its own OGL state via [React context](https://reactjs.org/docs/context.html) and a [Zustand](https://github.com/pmndrs/zustand) store, as defined by `RootState`. This can be accessed and modified with the `onCreated` canvas prop, and with hooks like `useOGL`.\n\n```tsx\ninterface RootState {\n  // Zustand setter and getter for live state manipulation.\n  // See https://github.com/pmndrs/zustand\n  get(): RootState\n  set(fn: (previous: RootState) =\u003e (next: Partial\u003cRootState\u003e)): void\n  // Canvas layout information\n  size: { width: number; height: number }\n  // OGL scene internals\n  renderer: OGL.Renderer\n  gl: OGL.OGLRenderingContext\n  scene: OGL.Transform\n  camera: OGL.Camera\n  // OGL perspective and frameloop preferences\n  orthographic: boolean\n  frameloop: 'always' | 'never'\n  // Internal XR manager to enable WebXR features\n  xr: XRManager\n  // Frameloop internals for custom render loops\n  priority: number\n  subscribed: React.RefObject\u003cSubscription\u003e[]\n  subscribe: (refCallback: React.RefObject\u003cSubscription\u003e, renderPriority?: number) =\u003e void\n  unsubscribe: (refCallback: React.RefObject\u003cSubscription\u003e, renderPriority?: number) =\u003e void\n  // Optional canvas event manager and its state\n  events?: EventManager\n  mouse: OGL.Vec2\n  raycaster: OGL.Raycast\n  hovered: Map\u003cnumber, Instance\u003cOGL.Mesh\u003e['object']\u003e\n}\n```\n\n### Accessing state via `useOGL`\n\nReturns the current canvas' `RootState`, describing react-ogl state and OGL rendering internals (see [root state](#root-state)).\n\n```tsx\nconst { renderer, gl, scene, camera, ... } = useOGL()\n```\n\nTo subscribe to a specific key, `useOGL` accepts a [Zustand](https://github.com/pmndrs/zustand) selector:\n\n```tsx\nconst renderer = useOGL((state) =\u003e state.renderer)\n```\n\n### Frameloop subscriptions via `useFrame`\n\nSubscribes an element into a shared render loop outside of React. `useFrame` subscriptions are provided a live `RootState`, the current RaF time in seconds, and a `XRFrame` when in a WebXR session. Note: `useFrame` subscriptions should never update React state but prefer external mechanisms like refs.\n\n```tsx\nconst object = React.useRef\u003cOGL.Transform\u003e(null!)\n\nuseFrame((state: RootState, time: number, frame?: XRFrame) =\u003e {\n  object.current.rotation.x = time / 2000\n  object.current.rotation.y = time / 1000\n})\n\nreturn \u003ctransform ref={object} /\u003e\n```\n\n### Loading assets via `useLoader`\n\nSynchronously loads and caches assets with a loader via suspense. Note: the caller component must be wrapped in `React.Suspense`.\n\n```jsx\nconst texture = useLoader(OGL.TextureLoader, '/path/to/image.jpg')\n```\n\nMultiple assets can be requested in parallel by passing an array:\n\n```jsx\nconst [texture1, texture2] = useLoader(OGL.TextureLoader, ['/path/to/image1.jpg', '/path/to/image2.jpg'])\n```\n\nCustom loaders can be implemented via the `LoaderRepresentation` signature:\n\n```tsx\nclass CustomLoader {\n  async load(gl: OGLRenderingContext, url: string): Promise\u003cvoid\u003e {}\n}\n\nconst result = useLoader(CustomLoader, '/path/to/resource')\n```\n\n### Object traversal via `useGraph`\n\nTraverses an `OGL.Transform` for unique meshes and programs, returning an `ObjectMap`.\n\n```tsx\nconst { nodes, programs } = useGraph(object)\n\n\u003cmesh geometry={nodes['Foo'].geometry} program={programs['Bar']} /\u003e\n```\n\n### Transient updates via `useStore`\n\nReturns the internal [Zustand](https://github.com/pmndrs/zustand) store. Useful for transient updates outside of React (e.g. multiplayer/networking).\n\n```tsx\nconst store = useStore()\nReact.useLayoutEffect(() =\u003e store.subscribe(state =\u003e ...), [store])\n```\n\n### Access internals via `useInstanceHandle`\n\nExposes an object's react-internal `Instance` state from a ref.\n\n\u003e **Note**: this is an escape hatch to react-internal fields. Expect this to change significantly between versions.\n\n```tsx\nconst ref = React.useRef\u003cOGL.Transform\u003e()\nconst instance = useInstanceHandle(ref)\n\nReact.useLayoutEffect(() =\u003e {\n  instance.parent.object.foo()\n}, [])\n\n\u003ctransform ref={ref} /\u003e\n```\n\n## Events\n\nreact-ogl implements mesh pointer-events with `OGL.Raycast` that can be tapped into via the following props:\n\n```tsx\n\u003cmesh\n  // Fired when the mesh is clicked or tapped.\n  onClick={(event: OGLEvent\u003cMouseEvent\u003e) =\u003e ...}\n  // Fired when a pointer becomes inactive over the mesh.\n  onPointerUp={(event: OGLEvent\u003cPointerEvent\u003e) =\u003e ...}\n  // Fired when a pointer becomes active over the mesh.\n  onPointerDown={(event: OGLEvent\u003cPointerEvent\u003e) =\u003e ...}\n  // Fired when a pointer moves over the mesh.\n  onPointerMove={(event: OGLEvent\u003cPointerEvent\u003e) =\u003e ...}\n  // Fired when a pointer enters the mesh's bounds.\n  onPointerOver={(event: OGLEvent\u003cPointerEvent\u003e) =\u003e ...}\n  // Fired when a pointer leaves the mesh's bounds.\n  onPointerOut={(event: OGLEvent\u003cPointerEvent\u003e) =\u003e ...}\n/\u003e\n```\n\nEvents contain the original event as `nativeEvent` and properties from `OGL.RaycastHit`.\n\n```tsx\n{\n  nativeEvent: PointerEvent | MouseEvent,\n  localPoint: Vec3,\n  distance: number,\n  point: Vec3,\n  faceNormal: Vec3,\n  localFaceNormal: Vec3,\n  uv: Vec2,\n  localNormal: Vec3,\n  normal: Vec3,\n}\n```\n\n### Custom events\n\nCustom events can be implemented per the `EventManager` interface and passed via the `events` Canvas prop.\n\n```tsx\nconst events: EventManager = {\n  connected: false,\n  connect(canvas: HTMLCanvasElement, state: RootState) {\n    // Bind handlers\n  },\n  disconnect(canvas: HTMLCanvasElement, state: RootState) {\n    // Cleanup\n  },\n}\n\n\u003cCanvas events={events}\u003e\n  \u003cmesh onPointerMove={(event: OGLEvent\u003cPointerEvent\u003e) =\u003e console.log(event)}\u003e\n    \u003cbox /\u003e\n    \u003cnormalProgram /\u003e\n  \u003c/mesh\u003e\n\u003c/Canvas\u003e\n```\n\n\u003cdetails\u003e\n  \u003csummary\u003eFull example\u003c/summary\u003e\n\n```tsx\nconst events = {\n  connected: false,\n  connect(canvas: HTMLCanvasElement, state: RootState) {\n    state.events.handlers = {\n      pointermove(event: PointerEvent) {\n        // Convert mouse coordinates\n        state.mouse.x = (event.offsetX / state.size.width) * 2 - 1\n        state.mouse.y = -(event.offsetY / state.size.height) * 2 + 1\n\n        // Filter to interactive meshes\n        const interactive: OGL.Mesh[] = []\n        state.scene.traverse((node: OGL.Transform) =\u003e {\n          // Mesh has registered events and a defined volume\n          if (\n            node instanceof OGL.Mesh \u0026\u0026\n            (node as Instance\u003cOGL.Mesh\u003e['object']).__handlers \u0026\u0026\n            node.geometry?.attributes?.position\n          )\n            interactive.push(node)\n        })\n\n        // Get elements that intersect with our pointer\n        state.raycaster!.castMouse(state.camera, state.mouse)\n        const intersects: OGL.Mesh[] = state.raycaster!.intersectMeshes(interactive)\n\n        // Call mesh handlers\n        for (const entry of intersects) {\n          if ((entry as unknown as any).__handlers) {\n            const object = entry as Instance\u003cOGL.Mesh\u003e['object']\n            const handlers = object.__handlers\n\n            const handlers = object.__handlers\n            handlers?.onPointerMove?.({ ...object.hit, nativeEvent: event })\n          }\n        }\n      },\n    }\n\n    // Bind\n    state.events.connected = true\n    for (const [name, handler] of Object.entries(state.events.handlers)) {\n      canvas.addEventListener(name, handler)\n    }\n  },\n  disconnect(canvas: HTMLCanvasElement, state: RootState) {\n    // Unbind\n    state.events.connected = false\n    for (const [name, handler] of Object.entries(state.events.handlers)) {\n      canvas.removeEventListener(name, handler)\n    }\n  },\n}\n\n\u003cCanvas events={events}\u003e\n  \u003cmesh onPointerMove={(event: OGLEvent\u003cPointerEvent\u003e) =\u003e console.log(event)}\u003e\n    \u003cbox /\u003e\n    \u003cnormalProgram /\u003e\n  \u003c/mesh\u003e\n\u003c/Canvas\u003e\n```\n\n\u003c/details\u003e\n\n## Portals\n\nPortal children into a foreign OGL element via `createPortal`, which can modify children's `RootState`. This is particularly useful for postprocessing and complex render effects.\n\n```tsx\nfunction Component {\n  // scene \u0026 camera are inherited from portal parameters\n  const { scene, camera, ... } = useOGL()\n}\n\nconst scene = new OGL.Transform()\nconst camera = new OGL.Camera()\n\n\u003ctransform\u003e\n  {createPortal(\u003cComponent /\u003e, scene, { camera })\n\u003c/transform\u003e\n```\n\n## Testing\n\nIn addition to `createRoot` (see [custom canvas](#custom-canvas)), react-ogl exports an `act` method which can be used to safely flush async effects in tests. The following emulates a legacy root and asserts against `RootState` (see [root state](#root-state)).\n\n```tsx\nimport * as React from 'react'\nimport * as OGL from 'ogl'\nimport { type Root, type RootStore, type RootState, createRoot } from 'react-ogl'\n\nit('tests against a react-ogl component or scene', async () =\u003e {\n  const transform = React.createRef\u003cOGL.Transform\u003e()\n\n  const root: Root = createRoot(document.createElement('canvas'))\n  const store: RootStore = await React.act(async () =\u003e root.render(\u003ctransform ref={transform} /\u003e))\n  const state: RootState = store.getState()\n\n  expect(transform.current).toBeInstanceOf(OGL.Transform)\n  expect(state.scene.children).toStrictEqual([transform.current])\n})\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpmndrs%2Freact-ogl","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpmndrs%2Freact-ogl","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpmndrs%2Freact-ogl/lists"}