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

feat: React 19 #39

Merged
merged 2 commits into from
Feb 18, 2025
Merged
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
28 changes: 13 additions & 15 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,7 @@
"repository": "https://github.com/pmndrs/its-fine",
"license": "MIT",
"files": [
"dist/*",
"src/*"
"dist/*"
],
"type": "module",
"types": "./dist/index.d.ts",
Expand All @@ -27,23 +26,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"
"@types/node": "^22.13.4",
"@types/react": "^19.0.0",
"@types/react-test-renderer": "^19.0.0",
"react": "^19.0.0",
"react-nil": "^2.0.0",
"react-test-renderer": "^19.0.0",
"rimraf": "^6.0.1",
"typescript": "^5.7.3",
"vite": "^6.1.0",
"vitest": "^3.0.6"
},
"dependencies": {
"@types/react-reconciler": "^0.28.0"
"@types/react-reconciler": "^0.28.9"
},
"peerDependencies": {
"react": ">=18.0"
"react": "^19.0.0"
},
"scripts": {
"build": "rimraf dist && vite build && tsc",
Expand Down
15 changes: 12 additions & 3 deletions tests/index.test.tsx → src/index.test.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import * as React from 'react'
import { describe, expect, it } from 'vitest'
import { type NilNode, type HostContainer, act, render, createPortal } from 'react-nil'
import { type NilNode, type HostContainer, render, createPortal } from 'react-nil'
import { create } from 'react-test-renderer'
import {
type Fiber,
Expand All @@ -11,7 +11,14 @@ import {
useNearestParent,
useContextBridge,
FiberProvider,
} from '../src'
} from './index'

declare global {
var IS_REACT_ACT_ENVIRONMENT: boolean
}

// Let React know that we'll be testing effectful components
global.IS_REACT_ACT_ENVIRONMENT = true

interface ReactProps {
key?: React.Key
Expand All @@ -25,7 +32,7 @@ interface PrimitiveProps {

type Primitive = NilNode<PrimitiveProps>

declare global {
declare module 'react' {
namespace JSX {
interface IntrinsicElements {
primitive: ReactProps & PrimitiveProps
Expand All @@ -41,6 +48,8 @@ class ClassComponent extends React.Component<{ children?: React.ReactNode }> {
}
}

const act = React.act

describe('useFiber', () => {
it('throws when used outside of a FiberProvider', async () => {
let threw = false
Expand Down
48 changes: 19 additions & 29 deletions src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,10 @@ import type ReactReconciler from 'react-reconciler'
*
* @see https://github.com/facebook/react/issues/14927
*/
const useIsomorphicLayoutEffect =
typeof window !== 'undefined' && (window.document?.createElement || window.navigator?.product === 'ReactNative')
? React.useLayoutEffect
: React.useEffect
const useIsomorphicLayoutEffect = /* @__PURE__ */ (() =>
typeof window !== 'undefined' && (window.document?.createElement || window.navigator?.product === 'ReactNative'))()
? React.useLayoutEffect
: React.useEffect

/**
* Represents a react-internal Fiber node.
Expand Down Expand Up @@ -75,18 +75,7 @@ function wrapContext<T>(context: React.Context<T>): React.Context<T> {
}
}

const error = console.error
console.error = function () {
const message = [...arguments].join('')
if (message?.startsWith('Warning:') && message.includes('useContext')) {
console.error = error
return
}

return error.apply(this, arguments as any)
}

const FiberContext = wrapContext(React.createContext<Fiber>(null!))
const FiberContext = /* @__PURE__ */ wrapContext(/* @__PURE__ */ React.createContext<Fiber>(null!))

/**
* A react-internal {@link Fiber} provider. This component binds React children to the React Fiber tree. Call its-fine hooks within this.
Expand Down Expand Up @@ -153,10 +142,10 @@ export function useContainer<T = any>(): T | undefined {
*/
export function useNearestChild<T = any>(
/** An optional element type to filter to. */
type?: keyof JSX.IntrinsicElements,
): React.MutableRefObject<T | undefined> {
type?: keyof React.JSX.IntrinsicElements,
): React.RefObject<T | undefined> {
const fiber = useFiber()
const childRef = React.useRef<T>()
const childRef = React.useRef<T>(undefined)

useIsomorphicLayoutEffect(() => {
childRef.current = traverseFiber<T>(
Expand All @@ -176,10 +165,10 @@ export function useNearestChild<T = any>(
*/
export function useNearestParent<T = any>(
/** An optional element type to filter to. */
type?: keyof JSX.IntrinsicElements,
): React.MutableRefObject<T | undefined> {
type?: keyof React.JSX.IntrinsicElements,
): React.RefObject<T | undefined> {
const fiber = useFiber()
const parentRef = React.useRef<T>()
const parentRef = React.useRef<T>(undefined)

useIsomorphicLayoutEffect(() => {
parentRef.current = traverseFiber<T>(
Expand All @@ -196,6 +185,11 @@ export type ContextMap = Map<React.Context<any>, any> & {
get<T>(context: React.Context<T>): T | undefined
}

const REACT_CONTEXT_TYPE = Symbol.for('react.context')

const isContext = <T,>(type: unknown): type is React.Context<T> =>
type !== null && typeof type === 'object' && '$$typeof' in type && type.$$typeof === REACT_CONTEXT_TYPE

/**
* Returns a map of all contexts and their values.
*/
Expand All @@ -207,13 +201,9 @@ export function useContextMap(): ContextMap {
contextMap.clear()
let node = fiber
while (node) {
if (node.type && typeof node.type === 'object') {
// https://github.com/facebook/react/pull/28226
const enableRenderableContext = node.type._context === undefined && node.type.Provider === node.type
const context = enableRenderableContext ? node.type : node.type._context
if (context && context !== FiberContext && !contextMap.has(context)) {
contextMap.set(context, React.useContext(wrapContext(context)))
}
const context = node.type
if (isContext(context) && context !== FiberContext && !contextMap.has(context)) {
contextMap.set(context, React.use(wrapContext(context)))
}

node = node.return!
Expand Down
11 changes: 0 additions & 11 deletions tests/setupTests.ts

This file was deleted.

5 changes: 3 additions & 2 deletions tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"target": "es6",
"module": "ESNext",
"lib": ["ESNext", "dom"],
"moduleResolution": "node",
"moduleResolution": "bundler",
"esModuleInterop": true,
"jsx": "react",
"pretty": true,
Expand All @@ -13,5 +13,6 @@
"emitDeclarationOnly": true,
"forceConsistentCasingInFileNames": true
},
"include": ["src/**/*"]
"include": ["src/**/*"],
"exclude": ["src/**/*.test.*"]
}
13 changes: 1 addition & 12 deletions vite.config.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,8 @@
/// <reference types="vitest" />
import * as path from 'path'
import * as path from 'node:path'
import { defineConfig } from 'vite'

export default defineConfig({
test: {
dir: 'tests',
setupFiles: 'tests/setupTests.ts',
},
build: {
minify: false,
sourcemap: true,
target: 'es2018',
lib: {
Expand All @@ -18,11 +12,6 @@ export default defineConfig({
},
rollupOptions: {
external: (id: string) => !id.startsWith('.') && !path.isAbsolute(id),
treeshake: false,
output: {
preserveModules: true,
sourcemapExcludeSources: true,
},
},
},
})
Loading