Is it possible render something before the component will unmount? - reactjs

I created a custom Modal. In the component mount I put the className "visible" with a simple opacity: 1.
Since the Modal has a transition, I also want to put a className "gone" with opacity 0 in the unmount of the component.
Is it possibile wait the transition of the close before unmount it and do something like this:
useEffect(() => {
setClassname('visible');
return () => {
setClassname('gone');
setTimeout(()=>{
OK, NOW U CAN UNMOUNT!
), 200 // time of the transition}
}
}, []);
?

I think React Transition Group can help you. https://reactcommunity.org/react-transition-group/
You should not remove class in unmount, just do this logic in parent component, using React Transition Group. You can find an example
of transition here https://reactcommunity.org/react-transition-group/transition

Related

How to start animation before component is removed in React?

I would like to know how do I trigger an animation before removing a functional React component.
I am using the npm package to manage animations via JS. So I just need to call the function. For example function animate.
import React from "react";
useEffect(() => {
function animateStart() {
console.log('AnimateStart')
}
},[]);
export default function () {
return(
<div className={'component'}/>
)
}
This is how I am triggering the animation when the component appears. I would like to somehow catch the removal and postpone it for the duration of the animation.
At the moment I'm calling the delete animation from the parent component. This forces me to store the animateExit function in another component. It is not comfortable :(
Try this:
useEffect(() => {
function animateStart() {
console.log('AnimateStart')
}
return () => {/* whatever you want to do, it will be called everytime when this component is unmounted*/}
},[]);

I thought react-spring (useSpring) causes the component to re-render a lot, but it may not and how do we make it so?

I thought react-spring useSpring() causes the component to re-render a lot, so if it is a component that already has a lot of CPU intensively work to do, then react-spring is not best suited for the task.
I can see that it re-renders a lot, in their example:
https://codesandbox.io/s/musing-dew-9tfi9?file=/src/App.tsx
(by looking at the console.log output, which has a lot of print out of renderCount. The print out is a lot more when we change the duration to 5000 which is 5 seconds).
Likewise, if it is a component that is similar to react-spring, it'd render a lot:
https://codesandbox.io/s/wonderful-currying-9pheq
However, the following code:
let renderCount = 0
export default function App() {
const styles = useSpring({
loop: true,
to: [
{ opacity: 1, color: '#ffaaee' },
{ opacity: 0.5, color: 'rgb(14,26,19)' },
{ transform: 'translateX(100px)' },
{ transform: 'translateX(0px)' },
],
from: { opacity: 0, color: 'red', transform: 'translateX(0px)' },
config: { duration: 500 },
})
console.log('renderCount', ++renderCount)
return <a.div style={styles}>I will fade in and out</a.div>
}
Demo: https://codesandbox.io/s/dazzling-rgb-j2bx3?file=/src/App.tsx
We can see that the renderCount hardly get printed out at all. react-spring should need to keep on updating the style of the component, so after a minute, I'd expect a lot of print out of renderCount like the first two examples above, but it does not.
How and why doesn't react-spring cause a lot of re-rendering in this case, and how do we know in what situation would react-spring cause a lot of re-rendering (and how to prevent it)?
react-spring updates styles incrementally to create animations (as opposed to css animations with transition).
Naive animations outside React
If react-spring was to exist outside of React (which it OBVIOUSLY doesn't because then it wouldn't be named react-spring), this could most easily be done by modifying a given element's style by means of Javascript according to some predetermined pattern based on multiple factors (like delay, duration, etc....). One scenario could be
...
setTimeout(() => document.getElementById("#el").style.opacity = 0.34,100)
setTimeout(() => document.getElementById("#el").style.opacity = 0.39,150)
setTimeout(() => document.getElementById("#el").style.opacity = 0.42,200)
...
setTimeout(() => document.getElementById("#el").style.opacity = 1.0, 1000)
Exactly how this would be implemented is of course not the point of this answer and the above would be a very naive implementation, but this is basically what could go on if we wanted to make some animated transition where the interpolation between two endpoints would be calculated and implemented by ourselves (using spring physics) as opposed to in the browser (with css transition).
Naive animations in React
In React, we know that the preferred way to do things is to provide changes inside React, which React then processes after which necessary changes to the DOM is handled by React. Taking the previous (naive) example to React, this would imply some scheme where a state storing the opacity would be updated repeatedly until the desired endpoint was reached.
const Component = () => {
...
const [opacity, setOpacity] = useState(0)
useEffect(() => {
...
setTimeout(() => setOpacity(0.34),100)
setTimeout(() => setOpacity(0.39),150)
setTimeout(() => setOpacity(0.42),200)
...
setTimeout(() => setOpacity(1.0), 1000)
}, [])
return (
<div style={{ opacity }} ... />
)
}
This would work, but as one would expect, it could be quite burdensome since animations are supposed to happen fast and smooth and React rerendering on every animation frame could be problematic; if the component within which animation took place was expensive to render, the animation itself could be suffering and not look very good.
react-spring in React
The solution to this problem by react-spring is to do updates OUTSIDE of React via refs instead. The previous toy example could look like:
const Component = () => {
...
const ref = useRef(null)
useEffect(() => {
if(ref.current) {
...
setTimeout(() => ref.current.style.opacity = 0.34,100)
setTimeout(() => ref.current.style.opacity = 0.39,150)
setTimeout(() => ref.current.style.opacity = 0.42,200)
...
setTimeout(() => ref.current.style.opacity = 1.0, 1000)
}
}, [])
...
return (
<div ref={ref} ... />
)
}
Again, this is an example, exactly how one would implement this in the best way (as in react-spring) is a different story. But we can agree on that if we would log to the console every time the above component rendered, it would only log once even though the opacity would continue to change.
To sum up, when react-spring is used optimally, it uses refs to update properties on DOM elements whereupon React is by-passed. Thanks to this, a component may render only once but still make repeated animations. This particularly applies to the situation when the api is used to perform updates (as oppose to storing a state in a parent component which is set every time we want an animation to take place):
const [spring, api] = useSpring(() => ({ <INITIAL PROPS> })) // use api to make changes
const spring = useSpring({ <INITIAL PROPS }) // rerender component to update props
When using the basic HTML elements supplied by react-spring (such as animated.div, animated.span etc...), react-spring takes care of attaching a ref on the corresponding DOM element and via this ref, it manages to animate the element and therefore also all the content in it. When creating your own custom component wrapped in animated, it is your concern to make sure that your custom component can take a ref (via forwardRef) and to pass it on to the element which should be animated, if you want optimal animations. If you don't do this, the element will be rerendered on every animation frame by react-spring. Even though this works too, it is suboptimal from a performance point of view.
Your examples
In your examples some other things are at play as well. In the first example, the hook useMeasure is being used from react-use-measure. This hook will continuously provide different values from the child component (here height is provided) whereupon the parent will rerender. Since Tree components are nested, whenever ONE Tree component changes height, all parent Tree components whose heights will be changed will also rerender. We therefore see quite a lot of rerendering. Also, because StrictMode is enabled, the number is doubled. useSpring is used without api but it doesn't matter here since the parent rerenders a lot due to useMeasure anyways.
In your second example with react-spring, the api is not used either but since the animation is looped, it doesn't require any state in the parent to be set and so it doesn't rerender. Because the parent doesn't rerender, the animated component doesn't rerender either and so also in this case, it doesn't matter if we use the api or not. In this example if we would like to update the animated props however, using the api to do so would cause the parent NOT to rerender whereas otherwise, the state holding the props to update to would reside in the parent (or a grand-parent) and so the animated component would rerender as well when the parent rerenders.

React 17.0.2 Functional Component Only Plays CSS Animation on First Render

I'd like to be able to display text feedback to a user after they answer a question and then fade out the text each time they answer. I'm trying a very simple situation to start out with where I play static feedback after every question. The text between the div is displayed and then fades after the initial rendering of the component but on subsequent renders the text is not displayed and the animation does not occur (I'm using Chrome). I can confirm the component is being re-rendered with Chrome Dev Tools after each cycle and I can see the text in the DOM. I'm using forwards so that at the end of the animation the text will stay invisible. The problem I'm trying to solve is why does the animation only occur after the first render cycle and what do I need to do in order to animate each time the component renders? Other details are that the app uses Redux and all components are functional.
Here's a sandbox that shows what the issue looks like. In this case I passed in props to the feedback component to force it to re-render. Each time you type into the text input it forces the feedback component to re-render (which I confirmed by logging to the console) but the animation only plays on the first render.
https://codesandbox.io/s/reactcssanimationissue-zwc6l?file=/src/UserFeedBack/UserFeedBackComponent.js
import React from "react";
import classes from "./AnswerFeedBackComponent.module.css";
const AnswerFeedBackComponent = () => {
return (
<div className={classes.CorrectAnswer} >Correct!</div>
);
}
export default AnswerFeedBackComponent;
---
.CorrectAnswer{
color:green;
font-weight: bold;
animation: fade 3s linear forwards;
opacity: 1;
}
#keyframes fade {
0% {
opacity: 1;
}
100%{
opacity: 0;
}
}
During re-render react looks for only the elements which have changed. If not, then it doesn't update the element, even if it rerenders.
So you can either make react know that this element have changed, or unmount and mount the component again. I have provided both the usecases.
You can add a key prop with a random value, so that react knows its different on each re-render and hence remounts the component. This is quick and hacky and it works.
const UserFeedBackComponent = (props) => {
console.log("UserFeedBackComponent rendered");
const rand = Math.random();
return (
<div className={classes.Border} key={rand}>
<div className={classes.CorrectAnswer}>Correct!</div>
</div>
);
};
https://codesandbox.io/s/reactcssanimationissue-forked-9jufd
Another way would be to remove it from the dom after its invisible. The logic will be handled by its parent, InputComponent
import React, { useState } from "react";
import UserFeedBackComponent from "../UserFeedBack/UserFeedBackComponent";
import classes from "./InputComponent.module.css";
const InputComponent = () => {
const [currentInput, setCurrentInput] = useState("");
const [showNotification, setShowNotification] = useState(false);
const inputHandler = (event) => {
setCurrentInput(event.target.value);
setShowNotification(true);
setTimeout(() => {
setShowNotification(false);
}, 3000);
};
return (
<React.Fragment>
<input type="text" value={currentInput} onChange={inputHandler} />
<div className={classes.Output}>{"Output is " + currentInput}</div>
{showNotification && <UserFeedBackComponent text={currentInput} />}
</React.Fragment>
);
};
export default InputComponent;
Every time you need to show the notification, you just need to set showNotification to true, and set it to false again via setTimeout which will fire after the duration of your animation i.e 3s. This is much better because, there's no stale invisible element, polluting your dom. You can still iterate and improve upon it.
https://codesandbox.io/s/reactcssanimationissue-forked-mejre

useeffect react cleanup not working with React Native modal

The effect for the modal looks like this:
useEffect(() => {
console.log('mounted')
return () => {
console.log('unounted')
}
}, [])
When I try to call the modal conditionally like this :
modal ? <Suspense fallback={<ActivityIndicator/>}><Modal /></Suspense> : null
the console shows mounted when modal===true but doesn't show unmounted when modal===false.What's going on here?Does functional component cleanups don't work in React Native?Or is there something else happening behind the curtain?
You should use the Modal's visible prop if you want to show the modal conditionally.
Add a state to the modal's father component like this:
const [isModalShown, setIsModalShown] = useState(false)
Then pass the state (isModalShown) to the prop and when you want to show the modal change the state to true (setIsModalShown(true))

Adding css class when component will mount

I add some class to the body element when a Modal is open
const Modal = ({ ... }) => {
...
useEffect(() => {
document.body.classList.add('modal-open')
return () => document.body.classList.remove('modal-open')
}, [])
But I notice there is a quick and short delay when applying the modal-open class (especially when this class contains some styles like margin-right: 17px, overflow-y: hidden and position: sticky !important) So i see the body element moving Which is not a good user experience of course.
So i moved adding the class out of the useEffect
document.body.classList.add('modal-open')
useEffect(() => { ... }, [])
And it is working but this line of code document.body.classList.add('modal-open') is executed at every re-render and not just once as within useEffect
So is there a better approch ? maybe componentWillMount equivalent in hooks cause i'm not touching the state i'm just manipulating dom elements classes ?
useLayoutEffect can be used instead of useEffect to apply changes earlier.
The signature is identical to useEffect, but it fires synchronously
after all DOM mutations. Use this to read layout from the DOM and
synchronously re-render. Updates scheduled inside useLayoutEffect will
be flushed synchronously, before the browser has a chance to paint.

Resources