Skip to content
2 changes: 1 addition & 1 deletion docs/app/components/Header/HeaderSidePanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import { MenuDocs } from '../Menu/MenuDocs'

import { NavigationSchema } from '../../../scripts/docs/navigation'
import { SiteThemePicker } from '../Site/SiteThemePicker'
import { forwardRef } from 'react'
import {
mainNavigation,
mobileDialogHeader,
Expand All @@ -21,6 +20,7 @@ import {
subNavContainer,
} from './HeaderSidePanel.css'
import { visuallyHidden } from '../../styles/utilities.css'
import { forwardRef } from 'react'

interface HeaderSidePanelProps {
isOpen: boolean
Expand Down
32 changes: 13 additions & 19 deletions docs/app/components/Text/Copy.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,23 +11,17 @@ export interface CopyProps {
tag?: keyof Pick<JSX.IntrinsicElements, 'p' | 'blockquote' | 'div' | 'label'>
}

export const Copy = forwardRef<
| HTMLHeadingElement
| HTMLQuoteElement
| HTMLDivElement
| HTMLLabelElement
| HTMLParagraphElement,
CopyProps
>(({ fontStyle = 'XS', className, children, tag = 'p' }, ref) => {
const Element = tag
export const Copy = forwardRef<any, CopyProps>(
({ fontStyle = 'XS', className, children, tag = 'p' }, ref) => {
const Element = tag

return (
<Element
className={clsx(FontSizes[fontStyle], copy, className)}
// @ts-expect-error – TODO: fix this
ref={ref}
>
{children}
</Element>
)
})
return (
<Element
className={clsx(FontSizes[fontStyle], copy, className)}
ref={ref}
>
{children}
</Element>
)
}
)
2 changes: 1 addition & 1 deletion docs/app/components/Text/Heading.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export interface HeadingProps {
style?: CSSProperties
}

export const Heading = forwardRef<HTMLHeadingElement, HeadingProps>(
export const Heading = forwardRef<any, HeadingProps>(
(
{
tag = 'h1',
Expand Down
3 changes: 1 addition & 2 deletions docs/app/components/Text/List.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,13 @@ export interface ListProps {
children?: ReactNode
}

export const List = forwardRef<HTMLUListElement | HTMLOListElement, ListProps>(
export const List = forwardRef<HTMLOListElement, ListProps>(
({ tag = 'ul', fontStyle = 'XS', className, children }, ref) => {
const Element = tag

return (
<Element
className={clsx(FontSizes[fontStyle], list, className)}
// @ts-expect-error - TODO: polymorphic refs, woo.
ref={ref}
>
{children}
Expand Down
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.1.0",
"@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.0",
"@types/react-dom": "19.0.0",
"@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/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,6 @@
"@react-spring/types": "~9.7.5"
},
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0 || ^18.0.0"
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
}
}
6 changes: 3 additions & 3 deletions packages/animated/src/withAnimated.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import * as React from 'react'
import { forwardRef, useRef, Ref, useCallback, useEffect } from 'react'
import { useRef, Ref, useCallback, useEffect, forwardRef } from 'react'
import {
is,
each,
Expand Down Expand Up @@ -27,7 +27,7 @@ export const withAnimated = (Component: any, host: HostConfig) => {
!is.fun(Component) ||
(Component.prototype && Component.prototype.isReactComponent)

return forwardRef((givenProps: any, givenRef: Ref<any>) => {
return forwardRef((givenProps: any, givenRef) => {
const instanceRef = useRef<any>(null)

// The `hasInstance` value is constant, so we can safely avoid
Expand Down 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
2 changes: 1 addition & 1 deletion packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,6 @@
"@react-spring/types": "~9.7.5"
},
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0 || ^18.0.0"
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
}
}
10 changes: 5 additions & 5 deletions packages/core/src/SpringContext.test.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import * as React from 'react'
import { render, RenderResult } from '@testing-library/react'
import { SpringContext } from './SpringContext'
import { SpringContextProvider, SpringContext } from './SpringContext'
import { SpringValue } from './SpringValue'
import { useSpring } from './hooks'

Expand All @@ -13,9 +13,9 @@ describe('SpringContext', () => {
}

const update = createUpdater(props => (
<SpringContext {...props}>
<SpringContextProvider {...props}>
<Child />
</SpringContext>
</SpringContextProvider>
))

it('only merges when changed', () => {
Expand All @@ -27,9 +27,9 @@ describe('SpringContext', () => {
}

const getRoot = () => (
<SpringContext {...context}>
<SpringContextProvider {...context}>
<Test />
</SpringContext>
</SpringContextProvider>
)

const expectUpdates = (updates: any[]) => {
Expand Down
41 changes: 18 additions & 23 deletions packages/core/src/SpringContext.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import * as React from 'react'
import { useContext, PropsWithChildren } from 'react'
import { useMemoOne } from '@react-spring/shared'

/**
* This context affects all new and existing `SpringValue` objects
Expand All @@ -13,33 +12,29 @@ export interface SpringContext {
immediate?: boolean
}

export const SpringContext = ({
export const SpringContext = React.createContext<SpringContext>({
pause: false,
immediate: false,
})

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

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

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

const { Provider } = ctx
return <Provider value={props}>{children}</Provider>
}

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

// Allow `useContext(SpringContext)` in TypeScript.
SpringContext.Provider = ctx.Provider
SpringContext.Consumer = ctx.Consumer

/** 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
const contextValue = React.useMemo(
() => ({ pause, immediate }),
[pause, immediate]
)
return (
<SpringContext.Provider value={contextValue}>
{children}
</SpringContext.Provider>
)
}
2 changes: 1 addition & 1 deletion packages/core/src/hooks/useInView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: 4 additions & 2 deletions packages/core/src/hooks/useSpring.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import * as React from 'react'
import { render, RenderResult } from '@testing-library/react'
import { is } from '@react-spring/shared'
import { Lookup } from '@react-spring/types'
import { SpringContext } from '../SpringContext'
import { SpringContextProvider, SpringContext } from '../SpringContext'
import { SpringValue } from '../SpringValue'
import { SpringRef } from '../SpringRef'
import { useSpring } from './useSpring'
Expand Down Expand Up @@ -140,7 +140,9 @@ function createUpdater(Component: React.ComponentType<{ args: [any, any?] }>) {
})

function renderWithContext(elem: JSX.Element) {
const wrapped = <SpringContext {...context}>{elem}</SpringContext>
const wrapped = (
<SpringContextProvider {...context}>{elem}</SpringContextProvider>
)
if (result) result.rerender(wrapped)
else result = render(wrapped)
return result
Expand Down
13 changes: 9 additions & 4 deletions packages/core/src/hooks/useSprings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@
// Create a local ref if a props function or deps array is ever passed.
const ref = useMemo(
() => (propsFn || arguments.length == 3 ? SpringRef() : void 0),
[]

Check warning on line 85 in packages/core/src/hooks/useSprings.ts

View workflow job for this annotation

GitHub Actions / Style Checks

React Hook useMemo has a missing dependency: 'propsFn'. Either include it or remove the dependency array
)

interface State {
Expand Down Expand Up @@ -124,11 +124,12 @@
})
},
}),
[]

Check warning on line 127 in packages/core/src/hooks/useSprings.ts

View workflow job for this annotation

GitHub Actions / Style Checks

React Hook useMemo has a missing dependency: 'forceUpdate'. Either include it or remove the dependency array
)

const ctrls = useRef([...state.ctrls])
const updates: any[] = []
const updates = useRef<any[]>(null!)
updates.current ??= []

// Cache old controllers to dispose in the commit phase.
const prevLength = usePrev(length) || 0
Expand All @@ -144,13 +145,13 @@
ctrls.current.length = length

declareUpdates(prevLength, length)
}, [length])

Check warning on line 148 in packages/core/src/hooks/useSprings.ts

View workflow job for this annotation

GitHub Actions / Style Checks

React Hook useMemo has missing dependencies: 'declareUpdates', 'prevLength', and 'ref'. Either include them or remove the dependency array

// Update existing controllers when "deps" are changed.
useMemo(() => {
declareUpdates(0, Math.min(prevLength, length))
// @ts-expect-error – we want to allow passing undefined to useMemo
}, deps)

Check warning on line 154 in packages/core/src/hooks/useSprings.ts

View workflow job for this annotation

GitHub Actions / Style Checks

React Hook useMemo has missing dependencies: 'declareUpdates', 'length', and 'prevLength'. Either include them or remove the dependency array

Check warning on line 154 in packages/core/src/hooks/useSprings.ts

View workflow job for this annotation

GitHub Actions / Style Checks

React Hook useMemo was passed a dependency list that is not an array literal. This means we can't statically verify whether you've passed the correct dependencies

/** Fill the `updates` array with declarative updates for the given index range. */
function declareUpdates(startIndex: number, endIndex: number) {
Expand All @@ -164,15 +165,17 @@
: (props as any)[i]

if (update) {
updates[i] = declareUpdate(update)
updates.current[i] = declareUpdate(update)
}
}
}

// New springs are created during render so users can pass them to
// their animated components, but new springs aren't cached until the
// commit phase (see the `useIsomorphicLayoutEffect` callback below).
const springs = ctrls.current.map((ctrl, i) => getSprings(ctrl, updates[i]))
const springs = ctrls.current.map((ctrl, i) =>
getSprings(ctrl, updates.current[i])
)

const context = useContext(SpringContext)
const prevContext = usePrev(context)
Expand Down Expand Up @@ -202,7 +205,7 @@
}

// Apply updates created during render.
const update = updates[i]
const update = updates.current[i]
if (update) {
// Update the injected ref if needed.
replaceRef(ctrl, update.ref)
Expand All @@ -214,6 +217,8 @@
} else {
ctrl.start(update)
}

updates.current[i] = null
}
})
})
Expand Down
7 changes: 2 additions & 5 deletions packages/core/test/setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,9 +60,6 @@ beforeEach(() => {
requestAnimationFrame: global.mockRaf.raf,
colors,
skipAnimation: false,
// This lets our useTransition hook force its component
// to update from within an "onRest" handler.
batchedUpdates: act,
})
})

Expand Down Expand Up @@ -138,7 +135,7 @@ global.advanceUntil = async test => {
willAdvance: observe,
})

jest.advanceTimersByTime(1000 / 60)
await act(() => jest.advanceTimersByTimeAsync(1000 / 60))
global.mockRaf.step()

// Stop observing after the frame is processed.
Expand All @@ -147,7 +144,7 @@ global.advanceUntil = async test => {
}

// Ensure pending effects are flushed.
await flushMicroTasks()
await act(() => flushMicroTasks())

// Prevent infinite recursion.
if (++steps > 1e3) {
Expand Down
4 changes: 2 additions & 2 deletions packages/parallax/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@
"@react-spring/web": "~9.7.5"
},
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0 || ^18.0.0",
"react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0"
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
"react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
}
}
6 changes: 3 additions & 3 deletions packages/parallax/src/index.tsx
Original file line number Diff line number Diff line change
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
Loading
Loading