Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor!: stateless useFiber, ContextBridge #32

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/CI.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,6 @@ jobs:

- name: Run tests
run: yarn test --silent

- name: Check for regressions
run: yarn lint
181 changes: 21 additions & 160 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,160 +12,27 @@
</a>
</p>

A collection of escape hatches exploring `React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED`.
A collection of low-level escape hatches for React.

As such, you can go beyond React's component abstraction; components are self-aware and can tap into the React Fiber tree. This enables powerful abstractions like stateless queries and sharing React Context across concurrent renderers. I'm sure you want me to tell you how safe and stable this all is.
As such, you can go beyond React's component abstraction; components are self-aware and can tap into the [React Fiber](https://youtu.be/ZCuYPiUIONs) tree. This enables powerful abstractions that can modify or extend React behavior without explicitly taking reconciliation into your own hands.

## Table of Contents

- [Components](#components)
- [FiberProvider](#fiberprovider)
- [Hooks](#hooks)
- [useFiber](#useFiber)
- [useContainer](#useContainer)
- [useNearestChild](#useNearestChild)
- [useNearestParent](#useNearestParent)
- [useContextMap](#useContextMap)
- [useContextBridge](#useContextBridge)
- [Utils](#utils)
- [traverseFiber](#traverseFiber)

## Components

### FiberProvider

A react-internal `Fiber` provider. This component binds React children to the React Fiber tree. Call its-fine hooks within this.

> **Note**: pmndrs renderers like react-three-fiber implement this internally to make use of [`useContextBridge`](#usecontextbridge), so you would only need this when using hooks inside of `react-dom` or `react-native`.

```tsx
import * as ReactDOM from 'react-dom/client'
import { FiberProvider, useFiber } from 'its-fine'

function App() {
const fiber = useFiber()
}

ReactDOM.createRoot(document.getElementById('root')!).render(
<FiberProvider>
<App />
</FiberProvider>,
)
```

## Hooks

Useful React hook abstractions for manipulating and querying from a component. These must be called within a [`FiberProvider`](#fiberprovider) component.

### useFiber
## useFiber

Returns the current react-internal `Fiber`. This is an implementation detail of [react-reconciler](https://github.com/facebook/react/tree/main/packages/react-reconciler).

```tsx
import * as React from 'react'
import { type Fiber, useFiber } from 'its-fine'

function Component() {
// Returns the current component's react-internal Fiber
const fiber: Fiber<null> | undefined = useFiber()
const fiber: Fiber = useFiber()

// function Component() {}
if (fiber) console.log(fiber.type)
}
```

### useContainer

Returns the current react-reconciler container info passed to `Reconciler.createContainer`.

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'
import { useContainer } from 'its-fine'

function Component() {
// Returns the current renderer's root container
const container: HTMLDivElement | undefined = useContainer<HTMLDivElement>()

// <div> (e.g. react-dom)
if (container) console.log(container)
}
```

### useNearestChild

Returns the nearest react-reconciler child instance or the node created from `Reconciler.createInstance`.

In react-dom, this would be a DOM element; in react-three-fiber this would be an `Instance` descriptor.

```tsx
import * as React from 'react'
import { useNearestChild } from 'its-fine'

function Component() {
// Returns a React Ref which points to the nearest child <div /> element.
// Omit the element type to match the nearest element of any kind
const childRef: React.MutableRefObject<HTMLDivElement | undefined> = useNearestChild<HTMLDivElement>('div')

// Access child Ref on mount
React.useEffect(() => {
// <div> (e.g. react-dom)
const child = childRef.current
if (child) console.log(child)
}, [])

// A child element, can live deep down another component
return <div />
console.log(fiber.type)
}
```

### useNearestParent

Returns the nearest react-reconciler parent instance or the node created from `Reconciler.createInstance`.

In react-dom, this would be a DOM element; in react-three-fiber this would be an instance descriptor.

```tsx
import * as React from 'react'
import { useNearestParent } from 'its-fine'

function Component() {
// Returns a React Ref which points to the nearest parent <div /> element.
// Omit the element type to match the nearest element of any kind
const parentRef: React.MutableRefObject<HTMLDivElement | undefined> = useNearestParent<HTMLDivElement>('div')

// Access parent Ref on mount
React.useEffect(() => {
// <div> (e.g. react-dom)
const parent = parentRef.current
if (parent) console.log(parent)
}, [])
}

// A parent element wrapping Component, can live deep up another component
;<div>
<Component />
</div>
```

### useContextMap

Returns a map of all contexts and their values.

```tsx
import * as React from 'react'
import { useContextMap } from 'its-fine'

const SomeContext = React.createContext<string>(null!)

function Component() {
const contextMap = useContextMap()
return contextMap.get(SomeContext)
}
```

### useContextBridge
## useContextBridge

React Context currently cannot be shared across [React renderers](https://reactjs.org/docs/codebase-overview.html#renderers) but explicitly forwarded between providers (see [react#17275](https://github.com/facebook/react/issues/17275)). This hook returns a `ContextBridge` of live context providers to pierce Context across renderers.

Expand All @@ -179,7 +46,7 @@ import * as ReactNil from 'react-nil'
// react-dom is a primary renderer that works on top of a secondary renderer.
// This also includes react-native, react-pixi, etc.
import * as ReactDOM from 'react-dom/client'
import { type ContextBridge, useContextBridge, FiberProvider } from 'its-fine'
import { type ContextBridge, useContextBridge } from 'its-fine'

function Canvas(props: { children: React.ReactNode }) {
// Returns a bridged context provider that forwards context
Expand All @@ -200,34 +67,28 @@ function Component() {
// Renders into a primary renderer like react-dom or react-native,
// DOMContext wraps Canvas and is bridged into Component
ReactDOM.createRoot(document.getElementById('root')!).render(
<FiberProvider>
<DOMContext.Provider value="Hello from react-dom">
<Canvas>
<Component />
</Canvas>
</DOMContext.Provider>
</FiberProvider>,
<DOMContext.Provider value="Hello from react-dom">
<Canvas>
<Component />
</Canvas>
</DOMContext.Provider>,
)
```

## Utils

Additional exported utility functions for raw handling of Fibers.

### traverseFiber
## traverse

Traverses up or down a `Fiber`, return `true` to stop and select a node.

```ts
import { type Fiber, traverseFiber } from 'its-fine'
import { type Fiber, traverse } from 'its-fine'

// Traverses through the Fiber tree, returns the current node when `true` is passed via selector
const parentDiv: Fiber<HTMLDivElement> | undefined = traverseFiber<HTMLDivElement>(
// Input Fiber to traverse
// Traverses down the Fiber tree, returns the current node when `true` is passed via selector
const parentDiv: Fiber | undefined = traverse(
// Input Fiber to traverse.
fiber as Fiber,
// Whether to ascend and walk up the tree. Will walk down if `false`
true,
// A Fiber node selector, returns the first match when `true` is passed
(node: Fiber<HTMLDivElement | null>) => node.type === 'div',
// A Fiber node selector, returns the first match when `true` is passed.
(node: Fiber) => node.type === 'div',
// Whether to ascend and walk up the tree. Will walk down if `false`. Default is `false`.
false,
)
```
22 changes: 9 additions & 13 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,26 +27,22 @@
"react-native": "./dist/index.js",
"sideEffects": false,
"devDependencies": {
"@types/node": "^18.7.15",
"@types/react": "^18.0.17",
"@types/react-test-renderer": "^18.0.0",
"react": "^18.2.0",
"react-nil": "^1.2.0",
"react-test-renderer": "^18.2.0",
"rimraf": "^3.0.2",
"suspend-react": "^0.0.8",
"typescript": "^4.7.4",
"vite": "^3.1.0",
"vitest": "^0.23.1"
"typescript": "^5.2.2",
"vite": "^4.5.0",
"vitest": "^0.34.6"
},
"dependencies": {
"@types/react-reconciler": "^0.28.0"
"@types/react": "*",
"@types/react-reconciler": "*"
},
"peerDependencies": {
"react": ">=18.0"
"react": ">=16.8"
},
"scripts": {
"build": "rimraf dist && vite build && tsc",
"test": "vitest run"
"build": "vite build",
"test": "vitest run --reporter verbose",
"lint": "tsc"
}
}
Loading
Loading