React 19 has removed the ReactDOM.findDOMNode method.
What would you like improved?
React 19 has removed the ReactDOM.findDOMNode method.
Please update and refactor performEnter method.
https://18.react.dev/reference/react-dom/findDOMNode
This can be worked around by ensuring you pass a nodeRef to all of the transitions:
function MyComponent() {
const nodeRef = React.useRef(null);
return (
<CSSTransition nodeRef={nodeRef} {...otherProps}>
<div ref={nodeRef}>...</div>
</CSSTransition>
);
}
@mogzol this bypasses the error, but also cuts the effect of the library... Is it still working normally for you?
In my case I use it for slidedown transitions, and I also added this fix you shared, but although error is gone, the transition effect is gone.
@sergioviniciuss yes it still works exactly how it did before. Make sure that the ref is set to the correct element, if you inspect the value, nodeRef.current should be the element that you want the classes applied to.
I have found that the above work around is functional when the top level element is an html element but not if it's a React component.
An example one that does not work:
function MyComponent() {
return (
<div>...</div>
);
}
function AnimateMyComponent() {
const nodeRef = React.useRef(null);
return (
<CSSTransition nodeRef={nodeRef} {...otherProps}>
<MyComponent ref={nodeRef} />
</CSSTransition>
);
};
This is fixed by adding the ref instead to the top level element that is an html element:
function MyComponent({ passDownRef }) {
return (
<div ref={passDownRef}>...</div>
);
};
function AnimateMyComponent() {
const nodeRef = React.useRef(null);
return (
<CSSTransition nodeRef={nodeRef} {...otherProps}>
<MyComponent passDownRef={nodeRef} />
</CSSTransition>
);
};
Passing this reference around may not be what you want to do, especially if you have an animate wrapper component like my codebase does. The solution I was able to find with my colleague was to use a context:
// AnimateMyComponent.jsx
import { Children, useRef, createContext } from "react";
import { CSSTransition } from "react-transition-group";
function AnimateMyComponent({ children }) {
const component = Children.only(children);
const nodeRef = useRef(null);
return (
<CSSTransition nodeRef={nodeRef} {...otherProps}>
<AnimationContext.Provider value={nodeRef}>
{component}
</AnimationContext.Provider>
</CSSTransition>
);
};
export default AnimateMyComponent;
export const AnimationContext = createContext(null);
// MyComponent.jsx
import AnimateMyComponent from "./AnimateMyComponent";
import MyFunctionalComponent from "./MyFunctionalComponent";
import MyClassComponent from "./MyClassComponent";
function MyComponent({ myBoolean }) {
return (
<div>
<AnimateMyComponent>
{myBoolean ? (
<MyFunctionalComponent />
) : (
<MyClassComponent />
)}
</AnimateMyComponent>
</div>
);
};
export default MyComponent;
// MyFunctionalComponent.jsx
import { useContext } from "react";
import { AnimationContext } from "./AnimateMyComponent";
function MyFunctionalComponent() {
const animationRef = useContext(AnimationContext);
return (
<div ref={animationRef}>...</div>
);
};
export default MyFunctionalComponent;
// what type AnimationContext would be in a typescript .tsx functional component
const animationRef = useContext(AnimationContext as React.Context<React.RefObject<HTMLElement>>);
// MyClassComponent.jsx
import { AnimationContext } from "./AnimateMyComponent";
export default class MyClassComponent extends React.Component {
render () {
return (
<AnimationContext.Consumer>
{(animationRef) => (
<div ref={animationRef}>...</div>
)}
</AnimationContext.Consumer>
);
}
};
I hope this is helpful to all and also to you specifically, @sergioviniciuss :D
Another option is to create a wrapper component that handles creating and passing nodeRef internally. The only issue with this is that it will wrap all of your transitions in a <div> (or whatever HTML element you use here). This could break styling or other things if your code isn't expecting these elements to be wrapped.
import React from "react";
import CSSTransition, { CSSTransitionProps } from "react-transition-group/CSSTransition";
export function CSSTransitionWrapped(props: React.PropsWithChildren<CSSTransitionProps>) {
const nodeRef = React.useRef<HTMLElement>(null);
const { children, ...propsRest } = props;
function setRef(value: HTMLDivElement | null) {
// Set the nodeRef to the first child of the div, emulating findDOMNode behavior
nodeRef.current = value?.firstElementChild as HTMLElement;
}
return (
<CSSTransition nodeRef={nodeRef} {...propsRest}>
<div ref={setRef}>{children}</div>
</CSSTransition>
);
}
Then use it exactly like CSSTransition was used before, it will work with HTML children or react component children:
Here the <div> will get transition classes applied to it:
<CSSTransitionWrapped {...transitionProps}>
<div>...</div>
</CSSTransitionWrapped>
And here the root element of SomeComponent will get the transition classes applied to it:
<CSSTransitionWrapped {...transitionProps}>
<SomeComponent />
</CSSTransitionWrapped>
Stumbled upon this. Looks like a modern alternative.
Here's the improved version of the above solution by @mogzol, without the need for an additional div wrapper.
Drawbacks:
- Custom components should accept and pass
refas a prop to their container element. - If
refis already specified onchildren, it will be overridden.
import { cloneElement, ReactElement, useRef } from 'react';
import { CSSTransition } from 'react-transition-group';
import { CSSTransitionProps } from 'react-transition-group/CSSTransition';
type Props = CSSTransitionProps & {
// intended behavior: https://github.com/facebook/react/issues/31824
// eslint-disable-next-line @typescript-eslint/no-explicit-any
children: ReactElement<any>;
};
function CSSTransitionWithRef({ children, ...restProps }: Props) {
const ref = useRef<HTMLElement>(null);
const setRef = (value: HTMLElement | null) => {
ref.current = value;
};
return (
<CSSTransition nodeRef={ref} {...restProps}>
{cloneElement(children, { ref: setRef })}
</CSSTransition>
);
}
export { CSSTransitionWithRef };
Another option is to create a wrapper component that handles creating and passing
nodeRefinternally. The only issue with this is that it will wrap all of your transitions in a<div>(or whatever HTML element you use here). This could break styling or other things if your code isn't expecting these elements to be wrapped.import React from "react"; import CSSTransition, { CSSTransitionProps } from "react-transition-group/CSSTransition";
export function CSSTransitionWrapped(props: React.PropsWithChildren<CSSTransitionProps>) { const nodeRef = React.useRef<HTMLElement>(null); const { children, ...propsRest } = props;
function setRef(value: HTMLDivElement | null) { // Set the nodeRef to the first child of the div, emulating findDOMNode behavior nodeRef.current = value?.firstElementChild as HTMLElement; }
return ( <CSSTransition nodeRef={nodeRef} {...propsRest}>
{children}</CSSTransition> ); } Then use it exactly like CSSTransition was used before, it will work with HTML children or react component children:Here the
<div>will get transition classes applied to it:<CSSTransitionWrapped {...transitionProps}>
...And here the root element of `SomeComponent` will get the transition classes applied to it:<CSSTransitionWrapped {...transitionProps}> <SomeComponent /> </CSSTransitionWrapped>
This works without unintended styling issues if you add style={{ display: 'contents' }} to the wrapping div. It will basically make the div not exist for the purposes of layout, but still works to mimic the findDOMNode behavior
Thanks @mogzol!
Props passed to the CSSTransitionWrapper meant to be pass-through were ending up on the wrapper div. To get them to be forwarded to the wrapped component instead:
<CSSTransition nodeRef={nodeRef} {...propsRest}>
{(status, childProps) => (
<div ref={setRef}>
{cloneElement(Children.only(children), childProps)}
</div>
)}
</CSSTransition>
Also, be careful if you are wrapping a component that renders using createPortal. findDOMNode handled this case, but the setRef function does not.