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 support #2349

Draft
wants to merge 23 commits into
base: next
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
2843b31
feat!: React 19 support
CodyJasonBennett Jan 2, 2025
8f36899
fix: remaining type errors
CodyJasonBennett Jan 2, 2025
4053dcc
chore: cleanup
CodyJasonBennett Jan 2, 2025
a3bd26d
docs: add missing react-use-measure dep
CodyJasonBennett Jan 2, 2025
a0652aa
chore: resolve conflicts
CodyJasonBennett Jan 8, 2025
f1b2617
docs: add missing use-gesture dep
CodyJasonBennett Jan 8, 2025
6cdab5e
chore: prefer JSX.IntrinsicElements for backwards compat
CodyJasonBennett Jan 8, 2025
a7fd0ac
chore: keep PropsWithRef for React 18
CodyJasonBennett Jan 8, 2025
f201223
chore(SpringContext): add React 18 backwards compat
CodyJasonBennett Jan 8, 2025
f25225d
fix(SpringContext): swap mutation target
CodyJasonBennett Jan 8, 2025
f3cef19
fix(SpringContext): typo
CodyJasonBennett Jan 8, 2025
9b12ea6
fix(SpringContext): feature check renderable context for consumer
CodyJasonBennett Jan 9, 2025
cafa5b8
chore: cleanup
CodyJasonBennett Jan 9, 2025
97b33db
experiment: revert dep upgrade
CodyJasonBennett Jan 9, 2025
20c7df5
experiment: restore dep upgrade
CodyJasonBennett Jan 9, 2025
6c06950
experiment: keep MutableRefObject
CodyJasonBennett Jan 10, 2025
33364f8
experiment: revert deps upgrade
CodyJasonBennett Jan 10, 2025
51312c3
Revert "experiment: keep MutableRefObject"
CodyJasonBennett Jan 10, 2025
59579e2
fix: add annotation for 18.3 immutable useRef
CodyJasonBennett Jan 10, 2025
a61d9e7
experiment: keep MutableRefObject
CodyJasonBennett Jan 10, 2025
983ca3f
experiment: upgrade deps to React 19
CodyJasonBennett Jan 10, 2025
c30d8a0
chore: upgrade to rc.2
CodyJasonBennett Jan 10, 2025
efda668
chore: cleanup
CodyJasonBennett Jan 10, 2025
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
Empty file modified .yarn/releases/yarn-3.8.7.cjs
100755 → 100644
Empty file.
2 changes: 2 additions & 0 deletions docs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
"@remix-run/serve": "2.15.2",
"@remix-run/server-runtime": "2.15.2",
"@supabase/supabase-js": "2.47.10",
"@use-gesture/react": "^10.3.1",
"@vanilla-extract/css": "1.17.0",
"@vanilla-extract/dynamic": "2.1.2",
"@vanilla-extract/recipes": "0.5.5",
Expand All @@ -44,6 +45,7 @@
"react": "18.3.1",
"react-dom": "18.3.1",
"react-select": "5.9.0",
"react-use-measure": "^2.1.1",
"zod": "3.24.1"
},
"devDependencies": {
Expand Down
10 changes: 5 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@
"@changesets/cli": "2.27.11",
"@commitlint/cli": "19.6.1",
"@commitlint/config-conventional": "19.6.0",
"@react-three/fiber": "8.17.10",
"@react-three/fiber": "9.0.0-rc.1",
"@remix-run/dev": "2.15.2",
"@simonsmith/cypress-image-snapshot": "9.1.0",
"@swc/core": "1.10.4",
Expand All @@ -79,8 +79,8 @@
"@types/jest": "29.5.14",
"@types/lodash.clamp": "4.0.9",
"@types/lodash.shuffle": "4.2.9",
"@types/react": "18.3.18",
"@types/react-dom": "18.3.5",
"@types/react": "19.0.2",
"@types/react-dom": "19.0.2",
"@types/react-lazyload": "3.2.3",
"@types/react-native": "0.73.0",
"@types/styled-components": "5.1.34",
Expand All @@ -95,8 +95,8 @@
"mock-raf": "npm:@react-spring/[email protected]",
"prettier": "3.4.2",
"pretty-quick": "4.0.0",
"react": "18.3.1",
"react-dom": "18.3.1",
"react": "19.0.0",
"react-dom": "19.0.0",
"react-konva": "18.2.10",
"react-native": "0.76.5",
"react-zdog": "1.2.2",
Expand Down
2 changes: 1 addition & 1 deletion packages/animated/src/withAnimated.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ export const withAnimated = (Component: any, host: HostConfig) => {

const observer = new PropsObserver(callback, deps)

const observerRef = useRef<PropsObserver>()
const observerRef = useRef<PropsObserver>(null)
useIsomorphicLayoutEffect(() => {
observerRef.current = observer

Expand Down
67 changes: 41 additions & 26 deletions packages/core/src/SpringContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,33 +13,48 @@ export interface SpringContext {
immediate?: boolean
}

export const SpringContext = ({
children,
...props
}: PropsWithChildren<SpringContext>) => {
const inherited = useContext(ctx)

// Inherited values are dominant when truthy.
const pause = props.pause || !!inherited.pause,
immediate = props.immediate || !!inherited.immediate

// Memoize the context to avoid unwanted renders.
props = useMemoOne(() => ({ pause, immediate }), [pause, immediate])

const { Provider } = ctx
return <Provider value={props}>{children}</Provider>
export const SpringContext = makeRenderableContext<
SpringContext,
PropsWithChildren<SpringContext>
>(
Context =>
({ children, ...props }) => {
const inherited = useContext(Context)

// Inherited values are dominant when truthy.
const pause = props.pause || !!inherited.pause
const immediate = props.immediate || !!inherited.immediate

// Memoize the context to avoid unwanted renders.
props = useMemoOne(() => ({ pause, immediate }), [pause, immediate])

return <Context value={props}>{children}</Context>
},
{} as SpringContext
)

interface RenderableContext<T, P> extends React.ProviderExoticComponent<P> {
Provider: RenderableContext<T, P>
Consumer: React.Consumer<T>
displayName?: string
}

const ctx = makeContext(SpringContext, {} as SpringContext)

// Allow `useContext(SpringContext)` in TypeScript.
SpringContext.Provider = ctx.Provider
SpringContext.Consumer = ctx.Consumer
CodyJasonBennett marked this conversation as resolved.
Show resolved Hide resolved

/** Make the `target` compatible with `useContext` */
function makeContext<T>(target: any, init: T): React.Context<T> {
Object.assign(target, React.createContext(init))
target.Provider._context = target
target.Consumer._context = target
return target
function makeRenderableContext<T, P>(
target: (context: React.Context<T>) => React.FunctionComponent<P>,
init: T
): RenderableContext<T, P> {
let context = React.createContext(init)
context = Object.assign(context, target(context))

// https://github.com/facebook/react/pull/28226
if ('_context' in context.Provider) {
context.Provider._context = target
} else {
context.Provider = context
}
// @ts-expect-error
context.Consumer._context = context

return context as unknown as RenderableContext<T, P>
}
1 change: 1 addition & 0 deletions packages/core/src/components/Spring.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { JSX } from 'react'
import { NoInfer, UnknownProps } from '@react-spring/types'
import { useSpring, UseSpringProps } from '../hooks/useSpring'
import { SpringValues, SpringToFn, SpringChain } from '../types'
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/components/Transition.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { JSX } from 'react'
import { Valid } from '../types/common'
import { TransitionComponentProps } from '../types'
import { useTransition } from '../hooks'
Expand Down
4 changes: 2 additions & 2 deletions packages/core/src/hooks/useInView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { Valid } from '../types/common'

export interface IntersectionArgs
extends Omit<IntersectionObserverInit, 'root' | 'threshold'> {
root?: React.MutableRefObject<HTMLElement>
root?: React.RefObject<HTMLElement>
once?: boolean
amount?: 'any' | 'all' | number | number[]
}
Expand All @@ -35,7 +35,7 @@ export function useInView<TElement extends HTMLElement>(
args?: IntersectionArgs
) {
const [isInView, setIsInView] = useState(false)
const ref = useRef<TElement>()
const ref = useRef<TElement>(null)

const propsFn = is.fun(props) && props

Expand Down
6 changes: 3 additions & 3 deletions packages/core/src/hooks/useResize.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { MutableRefObject } from 'react'
import { RefObject } from 'react'
import { onResize, each, useIsomorphicLayoutEffect } from '@react-spring/shared'

import { SpringProps, SpringValues } from '../types'

import { useSpring } from './useSpring'

export interface UseResizeOptions extends Omit<SpringProps, 'to' | 'from'> {
container?: MutableRefObject<HTMLElement | null | undefined>
container?: RefObject<HTMLElement | null | undefined>
}

/**
Expand All @@ -30,7 +30,7 @@ export interface UseResizeOptions extends Omit<SpringProps, 'to' | 'from'> {
```
*
* @param {UseResizeOptions} UseResizeOptions options for the useScroll hook.
* @param {MutableRefObject<HTMLElement>} UseResizeOptions.container the container to listen to scroll events on, defaults to the window.
* @param {RefObject<HTMLElement>} UseResizeOptions.container the container to listen to scroll events on, defaults to the window.
*
* @returns {SpringValues<{width: number; height: number;}>} SpringValues the collection of values returned from the inner hook
*/
Expand Down
6 changes: 3 additions & 3 deletions packages/core/src/hooks/useScroll.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { MutableRefObject } from 'react'
import { RefObject } from 'react'
import { each, onScroll, useIsomorphicLayoutEffect } from '@react-spring/shared'

import { SpringProps, SpringValues } from '../types'

import { useSpring } from './useSpring'

export interface UseScrollOptions extends Omit<SpringProps, 'to' | 'from'> {
container?: MutableRefObject<HTMLElement>
container?: RefObject<HTMLElement>
}

/**
Expand All @@ -30,7 +30,7 @@ export interface UseScrollOptions extends Omit<SpringProps, 'to' | 'from'> {
```
*
* @param {UseScrollOptions} useScrollOptions options for the useScroll hook.
* @param {MutableRefObject<HTMLElement>} useScrollOptions.container the container to listen to scroll events on, defaults to the window.
* @param {RefObject<HTMLElement>} useScrollOptions.container the container to listen to scroll events on, defaults to the window.
*
* @returns {SpringValues<{scrollX: number; scrollY: number; scrollXProgress: number; scrollYProgress: number}>} SpringValues the collection of values returned from the inner hook
*/
Expand Down
4 changes: 2 additions & 2 deletions packages/core/src/hooks/useSpring.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ interface TestContext extends SpringContext {
}

function createUpdater(Component: React.ComponentType<{ args: [any, any?] }>) {
let prevElem: JSX.Element | undefined
let prevElem: React.JSX.Element | undefined
let result: RenderResult | undefined

const context: TestContext = {
Expand All @@ -139,7 +139,7 @@ function createUpdater(Component: React.ComponentType<{ args: [any, any?] }>) {
}
})

function renderWithContext(elem: JSX.Element) {
function renderWithContext(elem: React.JSX.Element) {
const wrapped = <SpringContext {...context}>{elem}</SpringContext>
if (result) result.rerender(wrapped)
else result = render(wrapped)
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/types/transition.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ReactNode } from 'react'
import { ReactNode, JSX } from 'react'
import {
Lookup,
ObjectFromUnion,
Expand Down
10 changes: 5 additions & 5 deletions packages/parallax/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,8 @@ export interface IParallax {
current: number
controller: Controller<{ scroll: number }>
layers: Set<IParallaxLayer>
container: React.MutableRefObject<any>
content: React.MutableRefObject<any>
container: React.RefObject<any>
content: React.RefObject<any>
scrollTo(offset: number): void
update(): void
stop(): void
Expand Down Expand Up @@ -143,7 +143,7 @@ export const ParallaxLayer = React.memo(

React.useImperativeHandle(ref, () => layer)

const layerRef = useRef<any>()
const layerRef = useRef<any>(null)

const setSticky = (height: number, scrollTop: number) => {
const start = layer.sticky!.start! * height
Expand Down Expand Up @@ -229,8 +229,8 @@ export const Parallax = React.memo(
...rest
} = props

const containerRef = useRef<any>()
const contentRef = useRef<any>()
const containerRef = useRef<any>(null)
const contentRef = useRef<any>(null)

const state: IParallax = useMemoOne(
() => ({
Expand Down
2 changes: 1 addition & 1 deletion packages/shared/src/hooks/useMemoOne.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export function useMemoOne<T>(getResult: () => T, inputs?: any[]): T {
})
)

const committed = useRef<Cache<T>>()
const committed = useRef<Cache<T>>(null)
const prevCache = committed.current

let cache = prevCache
Expand Down
2 changes: 1 addition & 1 deletion packages/shared/src/hooks/usePrev.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { useEffect, useRef } from 'react'

/** Use a value from the previous render */
export function usePrev<T>(value: T): T | undefined {
const prevRef = useRef<any>()
const prevRef = useRef<any>(null)
useEffect(() => {
prevRef.current = value
})
Expand Down
4 changes: 2 additions & 2 deletions packages/types/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
*/

import * as React from 'react'
import { ReactElement, MutableRefObject } from 'react'
import { ReactElement, RefObject } from 'react'

/** Ensure each type of `T` is an array */
export type Arrify<T> = [T, T] extends [infer T, infer DT]
Expand Down Expand Up @@ -139,7 +139,7 @@ export interface Disposable {
}

// react.d.ts
export type RefProp<T> = MutableRefObject<T | null | undefined>
export type RefProp<T> = RefObject<T | null | undefined>

// https://github.com/DefinitelyTyped/DefinitelyTyped/issues/34237
export type ElementType<P = any> =
Expand Down
2 changes: 1 addition & 1 deletion targets/three/src/animated.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
import { CSSProperties, ForwardRefExoticComponent, FC } from 'react'
import { CSSProperties, ForwardRefExoticComponent, FC, JSX } from 'react'
import {
AssignableKeys,
ComponentPropsWithRef,
Expand Down
1 change: 0 additions & 1 deletion targets/three/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ addEffect(() => {
})

const host = createHost(primitives, {
// @ts-expect-error r3f related
applyAnimatedValues: applyProps,
})
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should probably be a proxy since it is valid to extend the JSX interface beyond what the THREE namespace contains.


Expand Down
2 changes: 1 addition & 1 deletion targets/three/src/primitives.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import * as THREE from 'three'
import '@react-three/fiber'
import { JSX } from 'react'

export type Primitives = keyof JSX.IntrinsicElements

Expand Down
2 changes: 2 additions & 0 deletions targets/web/src/primitives.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { JSX } from 'react'

export type Primitives = keyof JSX.IntrinsicElements
export const primitives: Primitives[] = [
'a',
Expand Down
Loading
Loading