findDOMNode warnings with CSSTransition components - reactjs

"react": "^16.13.1"
"react-transition-group": "^4.3.0"
<React.StrictMode>
<Router>
<App />
</Router>
</React.StrictMode>
Hi, everyone.
I faced with findDOMNode warning and can't find the right solution on the internet.
index.js:1 Warning: findDOMNode is deprecated in StrictMode. findDOMNode was passed an instance of Transition which is inside StrictMode...
This example I copied from off docs here and on click of the button, the same error appears.
const Toolbar = (props) => {
const [inProp, setInProp] = useState(false);
return (
<div>
<CSSTransition in={inProp} timeout={200} classNames="my-node">
<div>
{"I'll receive my-node-* classes"}
</div>
</CSSTransition>
<button type="button" onClick={() => setInProp(true)}>
Click to Enter
</button>
</div>
)
};
The solutions from the internet suggested trying createRef (I used usePef hook) but with Transitions, it didn't work.
It seems that React.StrictMode will throw a warning like this until the patch for this library will be merged, but still, I don't see the created issue
I will be grateful for any help or proposal of how to solve the issue

They fixed that bug from version 4.4.0.
To fix that can pass nodeRef to CSSTransition
const Toolbar = (props) => {
const [inProp, setInProp] = useState(false);
const nodeRef = useRef(null)
return (
<div>
<CSSTransition in={inProp} nodeRef={nodeRef} timeout={200} classNames="my-node">
<div ref={nodeRef}>
{"I'll receive my-node-* classes"}
</div>
</CSSTransition>
<button type="button" onClick={() => setInProp(true)}>
Click to Enter
</button>
</div>
)
};
I hope that will be helpful.

For anyone using class-based components, it's recommended to use createRef instead.
Example:
const nodeRef = React.createRef(null);
I was having the same issue, and also as #warkentien2 said, you also need to add the reference in the child component, otherwise nothing happens.

Related

CSS transition , react-transition-group and React.findDOMnode deprecation

I encountered a Warning relative to the findDOMnode deprecation when trying to use react routing in combination with react-transition-group, the warning stated:
index.js:1 Warning: findDOMNode is deprecated in StrictMode. findDOMNode was passed an instance of Transition which is inside StrictMode. Instead, add a ref directly to the element you want to reference. Learn more about using refs safely here: https://reactjs.org/link/strict-mode-find-node
The above warning refers to the following code:
<Route key={path} path={path} exact>
{({match})=>(
<CSSTransition
in={match!=null}
timeout={300}
classNames="page"
unmountOnExit
mountOnEnter
>
<div className="page">
<Component />
</div>
</CSSTransition>
)}
</Route>)
My first attempt of getting rid of that warning was to make use of useRef as suggested:
const nodeRef = useRef(null);
passing nodeRef as ref prop of the CSStransation element but the warning was still showing up.
For some reason I could only get rid of the warning by passing the triggering event that I was also using in the 'in' prop of the CSStransition element, like showed here below:
<Route key={path} path={path} exact>
{({match})=>(
<CSSTransition
ref={nodeRef}
in={match!=null}
timeout={300}
classNames="page"
unmountOnExit
mountOnEnter
key={match!=null} <------------ Adding this removed the warning
>
<div className="page">
<Component />
</div>
</CSSTransition>
)}
</Route>)
Everything work smoothly now but I cant really undestand why, and even if I remove the the ref from the CSStransition element I dont get any warning anymore.
Does anybody undestand why this is actually happening?
I spent a while trying to figure this out as well, and I finally got it! You need to use the nodeRef prop in the CSSTransition for each route individually. Each route gets its own ref, and that ref needs to be assigned to the nodeRef accordingly. I was able to get this working by using an array of refs, mapping each route and assigning the refs to the current index.
Take a look at this working example I made:
https://codesandbox.io/s/react-transition-routes-with-noderef-k9q47
And here's the block of code that is going to be the most helpful:
////// CONTENT TRANSITION ROUTER
const PageContent = withRouter(({ location }) => {
let routeRefs: any[] = [];
const isMatch = useCallback(
(path: string): boolean => {
return location.pathname === path ? true : false;
},
[location]
);
return (
<>
{appRoutes.map(({ path, Component }, index) => {
routeRefs[index] = React.useRef(null);
return (
<Route key={index} exact path={path}>
{() => {
// Route callback ensures the transitions are loaded correctly
return (
<CSSTransition
nodeRef={routeRefs[index]}
in={isMatch(path)}
timeout={300}
classNames="fade"
unmountOnExit
appear
>
<div ref={routeRefs[index]} className="fade">
<Component />
</div>
</CSSTransition>
);
}}
</Route>
);
})}
</>
);
});

Unexpected behavior of React render props

I've come up with the following code using React Transition Group:
const Transition = ({ elements, selectKey, render: Element, ...props }) => (
<TransitionGroup {...props}>
{elements.map((element) => (
<CSSTransition
key={selectKey(element)}
timeout={1000}
className="transition-slide"
>
<Element {...element} />
</CSSTransition>
))}
</TransitionGroup>
)
The key part here is that Transition component receives the render prop and renders it applying some transitions.
The way I expected for this to work:
<Transition render={(props) => <Toast {...props} />} />
But this code does not work as I expected: the transition of the next element interrupts the transition of the previous one.
However, this code works just fine:
const Element = (props) => <Toast {...props} />
// ...
<Transition render={Element} />
How can I fix this issue without putting the desired render-prop into a separate component?
Codesandbox: Example sandbox. The sandbox presents a non-working option with animation interruption. To get a working version, you need to uncomment lines 16 and 30 in the /Toasts/index.js file
P.S. I can't just use render={Toast} because I need to do ({id}) => <Toast dismiss={() => {deleteToast(id)}} />. I omitted this detail in order to simplify the understanding of the problem.
If you don't want to put the render function into another component, putting it into a useCallback() solved it for me.
const Toasts = () => {
const [toasts, addToast] = useToasts();
const Element = useCallback((props) => <Toast {...props} />, []);
return (
<div>
<button onClick={addToast}>Add toast</button>
<List>
<Transition
elements={toasts}
selectKey={({ id }) => id}
render={Element}
/>
</List>
</div>
);
}
(I don't quite understand the origin of the issue, but it has to do something with the function references.)

I used with Countup and VisibilitySensor, got "the prop is marked as required in 'countup'.." and "findDOMNode is deprecated in StrictMode..."error

import CountUp from "react-countup";
import VisibilitySensor from 'react-visibility-sensor';
function ... () {
<CountUp end={number.blah(get from api)} redraw={true}>
{({ countUpRef, start }) => (
<VisibilitySensor onChange={start} delayedCall>
<span ref={countUpRef} />
</VisibilitySensor>
)}
</CountUp>
}
I didn't use hooks, just used them directly in the main component.
Warning: Failed prop type: The prop end is marked as required in CountUp, but its value is undefined.
Warning: findDOMNode is deprecated in StrictMode. findDOMNode was passed an instance of VisibilitySensor which is inside StrictMode. Instead, add a ref directly to the element you want to reference.
These are the errors that occurred and how can I fix them please help
Saw this solution on a previous post
https://stackoverflow.com/a/71312117/1175555
And then I implemented it in this way
const { ref: slide1Ref, inView: slide1InView } = useInView();
And then the contents like
<Swiper
pagination={{
clickable: true
}}
modules={[Pagination]}
className={styles.swiper}
>
<SwiperSlide>
<div className={styles.slide1} ref={slide1Ref}>
{slide1InView && (
<div>
contents here
</div>
)}
</div>
</SwiperSlide>
This way, everytime I reach one of the slides, the countUps start working with no findDOMNode warnings. Like you I was using the now unmaintained VisibilitySensor
It works very well and with no issues.
Any questions, let me know!

Why do functional components wrapped in forwardRef receive null refs when I use the useContext hook?

I have a sidebarCart component basically a cart component and its is wrapped in forwardRef hook and contains useImperativeHandle to pass a function up to the parent which is App. Everything was working fine until I introduced the useContext hook and now the refs inside sidebarCart are becoming null, what is this weird behavior?
The refs I'm talking about are cartContainer and pageShdowCover
I discovered that when stop my nodejs server this problem disappears.
Here is the sidebarCart component.
import React ,{createRef,forwardRef,useImperativeHandle,useContext,useEffect}from 'react'
import CartItem from './CartItem'
import {MyContext} from '../../Context/ProductsProvider'
const SideBarCart=forwardRef((props,ref)=>{
const {setCart,cart} =useContext(MyContext)
const cartContainer = createRef()
const pageShdowCover = createRef()
const slideOut=e=>{
cartContainer.current.style.right='-400px'
pageShdowCover.current.style.opacity='0'
setTimeout(()=>pageShdowCover.current.style.display='none', 600);
}
useImperativeHandle(ref, () => ({
slideIn(){
pageShdowCover.current.style.display='block'
cartContainer.current.style.right='0'
pageShdowCover.current.style.opacity='1'
}
}),[pageShdowCover,cartContainer]);
return (
<div>
<div className="pageShdowCover" ref={pageShdowCover} ></div>
<div className="SideContainer cart" ref={cartContainer}>
<div className="cart__top">
<h1>Cart</h1>
<i className="far fa-times-circle Close" onClick={slideOut}></i>
<a onClick={slideOut}>x</a>
</div>
<div className="cart__body">
</div>
</div>
</div>
)
})
export default SideBarCart
I think that the useContext hook will result in children rerendering without the parent rerendering. Refs don't trigger rerenders when they are changed, so refs passed from the parent to children may be stale? (I think this is the answer - I may not be correct).
In summary (what worked for me): to solve the problem I was having, just store a ref in state to trigger re-renders when a ref changes.
===
Explanation
My use-case is that I have a parent component with 4 children. One of these children is a header, that I would like the siblings to be able to 'inject' controls into when they are rendered using ReactDOM.createPortal
- Parent
- Header
- A
- B
- C
I found that if I just used a ref, that when the ref is assigned to the DOM the parent isn't rerendered, therefore components A, B, C are not rerendered so they do not know about the newly assigned ref.current value.
I.E. The following does NOT work
const Parent = () => {
const ref = useRef()
return (
<div>
<Header ref={el => { ref.current = el } />
<C ref={ref} />
<B ref={ref} />
<C ref={ref} />
</div>
)
}
// A, B, C have the same signature
const A = forwardRef((props, ref) => {
return (
<div>
{createPortal(<SomeComponent />, ref.current)}
<OtherComponent />
</div>
)
})
I found the solution to this in THIS answer on StackOverflow. I came to this question because A, B, and C are using useContext(...). Previously for me, when the state was created in Parent instead of using a context, the above code WAS working - probably because the parent was rerendering and leaving stale refs around or something (I think it was working 'accidentally'). i.e. the problem wasn't related to context, but rather how rerenders are triggered.
This code DOES work
const Parent = () => {
const [ref, setRef] = useState()
return (
<div>
<Header ref={el => { setRef(el) } />
<C ref={ref} />
<B ref={ref} />
<C ref={ref} />
</div>
)
}
// A, B, C have the same signature
const A = forwardRef((props, ref) => {
return (
<div>
{createPortal(<SomeComponent />, ref)}
<OtherComponent />
</div>
)
})

findDOMNode is deprecated in StrictMode

I am using antd and I am seeing this error
findDOMNode is deprecated in StrictMode. findDOMNode was passed an instance of DOMWrap which is inside StrictMode. Instead, add a ref directly to the element you want to reference
I have realized that it is because of mode="horizontal".
I have tried using other components as well and I see this error a lot in antd. Is there any way to fix this issue?
This is my current code
import React from 'react'
import { connect } from 'react-redux';
import { Layout, Menu } from 'antd';
const { Header, Footer, Content } = Layout;
const AddForm = () => {
return (
<div>
{/* // Menu Starts from here */}
<Layout className="layout">
<Header>
<div className="logo" />
<Menu theme="dark" mode="horizontal" defaultSelectedKeys={['2']}>
<Menu.Item key="1">nav 1</Menu.Item>
<Menu.Item key="2">nav 2</Menu.Item>
<Menu.Item key="3">nav 3</Menu.Item>
</Menu>
</Header>
<Content style={{ padding: '0 50px' }}>
<div className="site-layout-content">Content</div>
</Content>
<Footer style={{ textAlign: 'center' }}>Ant Design ©2018 Created by Ant UED</Footer>
</Layout>
</div>
)
};
I'm fixing it by wrapping the commponent using <React.StrictMode>
in my case, the the button inside the form caused that error, so I solve it by
<React.StrictMode>
<Button type="primary" htmlType="submit" className="submit">
Register
</Button>
</React.StrictMode>
There is nothing you can do about it. You have to wait for antd to support Strict Mode. Yet, you can consider contributing to the project.
The semantic team has issued that they are fixing the issue:
See here https://github.com/Semantic-Org/Semantic-UI-React/issues/4050
The best work around I presume is using plain semantic-ui instead of hacking react.
<button className="ui button large primary">click me</button>
for anyone facing this warning, I managed to resolve it by upgrading ant to "antd": "4.20.7", and the issue disappeared. I am using react version 17.0.2.
I think it was resolved in newer release of antd, however, I could not find any reference online.
Hope this helps.
There is no good alternaive for findDOMNode in React 16: https://github.com/ant-design/ant-design/issues/22493
As a workaround, I am adding this at the root file.
I'm suppressing the warning like so:
// eslint-disable-next-line
const consoleError = console.error.bind(console);
// eslint-disable-next-line
console.error = (errObj, ...args) => {
if (
(process.env.NODE_ENV === 'development' && typeof errObj.message === 'string' && args.includes('findDOMNode')
) {
return;
}
consoleError(errObj, ...args);
};
I know this is not a fix, but at least my console is not bloated with findDOMNode messages. Until And-Design fixes this, this will do.

Resources