tunnel-rat

repository·main·Indexed 19 days ago

https://github.com/pmndrs/tunnel-rat

A utility for sending React elements between different React renderers, such as bridging a 3D @react-three/fiber Canvas and a standard HTML DOM UI. It provides a tunnel() function that creates In and Out components to teleport content across the component tree without prop drilling.

Tokens
1.7K
Snippets
7
Records
8
Agent score
16%

What's inside tunnel-rat

  1. How tunnel-rat works

    main
    tunnel-rat allows React elements to be sent from one part of an application and rendered in another, even across different React renderers. This is particularly useful for rendering HTML elements from within a @react-three/fiber application (or vice versa), where the two environments use separate renderers that typically cannot share standard DOM elements.
  2. Create and use a tunnel

    main

    To use tunnel-rat, first create a tunnel instance using the tunnel() function. This instance provides two components: In and Out.

    • <t.In>: Use this component to wrap the elements you want to send through the tunnel.
    • <t.Out />: Use this component at the destination to render the elements received from the tunnel.
    import tunnel from 'tunnel-rat'
    
    // 1. Create the tunnel
    const t = tunnel()
    
    // 2. Send elements into the tunnel
    const Sender = () => (
      <t.In>
        <h1>Very cool!</h1>
        <p>These will appear somewhere else!</p>
      </t.In>
    )
    
    // 3. Render elements at the destination
    const Receiver = () => (
      <t.Out />
    )
  3. Avoid element mismatching by using keys

    main

    When using multiple <t.In> components, React may lose track of object order or mismatch objects, especially if the elements being sent are of the same type. To prevent this, always use unique key props on the elements inside your <t.In> components. Treat multiple <t.In> instances as a list of items.

    // Always use keys to ensure correct ordering and matching
    <ui.In>
      <p key="foo">foo</p>
    </ui.In>
    
    <ui.In>
      <p key="bar">bar</p>
    </ui.In>
  4. Render 3D elements from a DOM UI into @react-three/fiber

    main

    The direction can be reversed: you can send 3D elements (like <mesh />) from a standard React component into a <Canvas /> using the tunnel.

    import { Canvas } from '@react-three/fiber'
    import tunnel from 'tunnel-rat'
    
    /* Create a tunnel. */
    const three = tunnel()
    
    const App = () => (
      <div>
        <div id="ui">
          {/* Send 3D elements into the R3F Canvas! */}
          <three.In>
            <mesh>
              <sphereGeometry />
              <meshBasicMaterial />
            </mesh>
          </three.In>
        </div>
    
        <Canvas>
          {/* Render anything sent through the tunnel! */}
          <three.Out />
        </Canvas>
      </div>
    )
  5. Render HTML from within @react-three/fiber

    main

    You can use tunnel-rat to bridge the gap between a standard DOM UI and a @react-three/fiber Canvas. This allows you to trigger HTML UI updates from within your 3D scene logic.

    import { Canvas } from '@react-three/fiber'
    import tunnel from 'tunnel-rat'
    
    /* Create a tunnel. */
    const ui = tunnel()
    
    const App = () => (
      <div>
        <div id="ui">
          {/* Destination for HTML elements */}
          <ui.Out />
        </div>
    
        <Canvas>
          {/* Send HTML elements into the tunnel from within the Canvas */}
          <ui.In>
            <p key="first">Hi, I'm a cube!</p>
          </ui.In>
    
          <mesh>
            <boxGeometry />
            <meshBasicMaterial />
          </mesh>
    
          <ui.In>
            <p key="second">And I'm a sphere!</p>
          </ui.In>
    
          <mesh>
            <sphereGeometry />
            <meshBasicMaterial />
          </mesh>
        </Canvas>
      </div>
    )
  6. Use tunnel() to teleport components across the component tree

    main

    The tunnel() function creates a communication channel between an In component and an Out component. This allows you to render children from one part of your React tree into a completely different location (the Out component's location) without prop drilling.

    1. Call tunnel() to get the In and Out components.
    2. Place the In component where you want to define the content to be teleported.
    3. Place the Out component where you want that content to actually appear in the DOM.

    Note: The order of rendered components in the Out component matches the order in which the In components are mounted in the tree.

    import tunnel from 'tunnel-rat'
    
    const { In, Out } = tunnel()
    
    function App() {
      return (
        <div>
          <div className="header">
            {/* This will appear in the Out component below */}
            <In>
              <h1>Hello from the tunnel!</h1>
            </In>
          </div>
    
          <div className="footer">
            {/* This is where the teleported content will render */}
            <Out />
          </div>
        </div>
      )
    }
  7. The In component

    main

    The In component is a provider for content that you want to teleport. It accepts children as a prop. When an In component mounts, it registers its children into the shared store created by tunnel(). When it unmounts, it removes them. It also uses a versioning mechanism to ensure that the re-rendering of the Out component correctly reflects the current order of mounted In components.

    <In>
      <MyComponent />
    </In>