diff --git a/README.md b/README.md
index 69c5eb5..9181b08 100644
--- a/README.md
+++ b/README.md
@@ -60,9 +60,9 @@ function Component() {
### useContainer
-Returns the current react-reconciler `Container` or the Fiber created from `Reconciler.createContainer`.
+Returns the current react-reconciler container info passed to `Reconciler.createContainer`.
-In react-dom, `container.containerInfo` will point to the root DOM element; in react-three-fiber, it will point to the root Zustand store.
+In react-dom, a container will point to the root DOM element; in react-three-fiber, it will point to the root Zustand store.
```tsx
import * as React from 'react'
@@ -73,7 +73,7 @@ function Component() {
React.useLayoutEffect(() => {
//
(e.g. react-dom)
- console.log(container.containerInfo)
+ console.log(container)
}, [container])
}
```
@@ -166,5 +166,10 @@ Traverses up or down through a `Fiber`, return `true` to stop and select a node.
import { type Fiber, traverseFiber } from 'its-fine'
const ascending = true
-const prevElement: Fiber = traverseFiber(fiber, ascending, (node: Fiber) => node.type === 'element')
+
+const parentDiv: Fiber
| undefined = traverseFiber(
+ fiber as Fiber,
+ ascending,
+ (node: Fiber) => node.type === 'div',
+)
```
diff --git a/src/index.tsx b/src/index.tsx
index 5ef8374..a0ff0de 100644
--- a/src/index.tsx
+++ b/src/index.tsx
@@ -4,25 +4,29 @@ import type ReactReconciler from 'react-reconciler'
/**
* Represents a react-internal Fiber node.
*/
-export type Fiber = ReactReconciler.Fiber
+export type Fiber = Omit & { stateNode: T }
/**
* Represents a {@link Fiber} node selector for traversal.
*/
-export type FiberSelector = (node: Fiber) => boolean | void
+export type FiberSelector = (node: Fiber) => boolean | void
/**
* Traverses up or down through a {@link Fiber}, return `true` to stop and select a node.
*/
-export function traverseFiber(fiber: Fiber, ascending: boolean, selector: FiberSelector): Fiber | undefined {
+export function traverseFiber(
+ fiber: Fiber,
+ ascending: boolean,
+ selector: FiberSelector,
+): Fiber | undefined {
let halted = false
- let selected: Fiber | undefined
+ let selected: Fiber | undefined
- let node = fiber[ascending ? 'return' : 'child']
+ let node = ascending ? fiber.return : fiber.child
let sibling = fiber.sibling
while (node) {
while (sibling) {
- halted ||= !!selector(sibling)
+ halted ||= selector(sibling) === true
if (halted) {
selected = sibling
break
@@ -31,13 +35,13 @@ export function traverseFiber(fiber: Fiber, ascending: boolean, selector: FiberS
sibling = sibling.sibling
}
- halted ||= !!selector(node)
+ halted ||= selector(node) === true
if (halted) {
selected = node
break
}
- node = node[ascending ? 'return' : 'child']
+ node = ascending ? node.return : node.child
}
return selected
@@ -54,28 +58,32 @@ const { ReactCurrentOwner } = (React as unknown as ReactInternal).__SECRET_INTER
/**
* Returns the current react-internal {@link Fiber}. This is an implementation detail of [react-reconciler](https://github.com/facebook/react/tree/main/packages/react-reconciler).
*/
-export function useFiber(): Fiber {
- const [fiber] = React.useState(() => ReactCurrentOwner.current!)
+export function useFiber(): Fiber {
+ const [fiber] = React.useState>(() => ReactCurrentOwner.current!)
return fiber
}
/**
- * Represents a reconciler container.
+ * Represents a react-reconciler container instance.
*/
-export interface Container extends Fiber {
- /** Represents container state passed to {@link ReactReconciler.Reconciler.createContainer}. */
+export interface ContainerInstance {
containerInfo: T
}
/**
- * Returns the current react-reconciler {@link Container} or the Fiber created from {@link ReactReconciler.Reconciler.createContainer}.
+ * Returns the current react-reconciler container info passed to {@link ReactReconciler.Reconciler.createContainer}.
*
- * In react-dom, {@link Container.containerInfo} will point to the root DOM element; in react-three-fiber, it will point to the root Zustand store.
+ * In react-dom, a container will point to the root DOM element; in react-three-fiber, it will point to the root Zustand store.
*/
-export function useContainer(): Container {
+export function useContainer(): T {
const fiber = useFiber()
const container = React.useMemo(
- () => traverseFiber(fiber, true, (node) => node.type == null && node.stateNode.containerInfo != null)!.stateNode,
+ () =>
+ traverseFiber>(
+ fiber,
+ true,
+ (node) => node.type == null && node.stateNode?.containerInfo != null,
+ )!.stateNode.containerInfo,
[fiber],
)
@@ -89,13 +97,13 @@ export function useContainer(): Container {
*/
export function useNearestChild(): React.MutableRefObject {
const fiber = useFiber()
- const instance = React.useRef()
+ const childRef = React.useRef()
React.useLayoutEffect(() => {
- instance.current = traverseFiber(fiber, false, (node) => typeof node.type === 'string')?.stateNode
+ childRef.current = traverseFiber(fiber, false, (node) => typeof node.type === 'string')?.stateNode
}, [fiber])
- return instance
+ return childRef
}
/**
@@ -105,13 +113,13 @@ export function useNearestChild(): React.MutableRefObject(): React.MutableRefObject {
const fiber = useFiber()
- const instance = React.useRef()
+ const parentRef = React.useRef()
React.useLayoutEffect(() => {
- instance.current = traverseFiber(fiber, true, (node) => typeof node.type === 'string')?.stateNode
+ parentRef.current = traverseFiber(fiber, true, (node) => typeof node.type === 'string')?.stateNode
}, [fiber])
- return instance
+ return parentRef
}
/**
diff --git a/tests/index.test.tsx b/tests/index.test.tsx
index b5c253d..bc0b6a3 100644
--- a/tests/index.test.tsx
+++ b/tests/index.test.tsx
@@ -1,11 +1,12 @@
import * as React from 'react'
import { describe, expect, it } from 'vitest'
-import { act, render, type HostContainer, type NilNode } from 'react-nil'
+import { type NilNode, type HostContainer, act, render } from 'react-nil'
import { create } from 'react-test-renderer'
import {
type Fiber,
+ type ContainerInstance,
+ traverseFiber,
useFiber,
- type Container,
useContainer,
useNearestChild,
useNearestParent,
@@ -22,6 +23,8 @@ interface PrimitiveProps {
name?: string
}
+type Primitive = NilNode
+
declare global {
namespace JSX {
interface IntrinsicElements {
@@ -75,27 +78,96 @@ describe('useFiber', () => {
})
})
+describe('traverseFiber', () => {
+ it('iterates descending through a fiber', async () => {
+ let fiber!: Fiber
+
+ function Test() {
+ fiber = useFiber()
+ return
+ }
+ await act(async () => {
+ render(
+
+
+ ,
+ )
+ })
+
+ const traversed = [] as unknown as [child: Fiber]
+ traverseFiber(fiber, false, (node) => void traversed.push(node))
+
+ expect(traversed.length).toBe(1)
+
+ const [child] = traversed
+ expect(child.stateNode.props.name).toBe('child')
+ })
+
+ it('iterates ascending through a fiber', async () => {
+ let fiber!: Fiber
+ let container!: HostContainer
+
+ function Test() {
+ fiber = useFiber()
+ return
+ }
+ await act(async () => {
+ container = render(
+
+
+ ,
+ )
+ })
+
+ const traversed = [] as unknown as [
+ parent: Fiber,
+ rootContainer: Fiber>,
+ ]
+ traverseFiber(fiber, true, (node) => void traversed.push(node))
+
+ expect(traversed.length).toBe(2)
+
+ const [parent, rootContainer] = traversed
+ expect(parent.stateNode.props.name).toBe('parent')
+ expect(rootContainer.stateNode.containerInfo).toBe(container)
+ })
+
+ it('returns the active node when halted', async () => {
+ let fiber!: Fiber
+ let container!: HostContainer
+
+ function Test() {
+ fiber = useFiber()
+ return
+ }
+ await act(async () => (container = render()))
+
+ const child = traverseFiber(fiber, false, (node) => node.stateNode === container.head)
+ expect(child!.stateNode.props.name).toBe('child')
+ })
+})
+
describe('useContainer', () => {
it('gets the current react-reconciler container', async () => {
- let currentContainer!: Container
+ let rootContainer!: HostContainer
let container!: HostContainer
function Test() {
- currentContainer = useContainer()
+ rootContainer = useContainer()
return null
}
await act(async () => (container = render()))
- expect(currentContainer.containerInfo).toBe(container)
+ expect(rootContainer).toBe(container)
})
})
describe('useNearestChild', () => {
it('gets the nearest child instance', async () => {
- const instances: React.MutableRefObject | undefined>[] = []
+ const instances: React.MutableRefObject[] = []
function Test(props: React.PropsWithChildren) {
- instances.push(useNearestChild())
+ instances.push(useNearestChild())
return <>{props.children}>
}
@@ -130,10 +202,10 @@ describe('useNearestChild', () => {
describe('useNearestParent', () => {
it('gets the nearest parent instance', async () => {
- const instances: React.MutableRefObject | undefined>[] = []
+ const instances: React.MutableRefObject[] = []
function Test(props: React.PropsWithChildren) {
- instances.push(useNearestParent())
+ instances.push(useNearestParent())
return <>{props.children}>
}