Animating conditionally rendered components in React - javascript

I have a functional component that renders a div X or a divY depending if the props are undefined or not.
Functional Component:
const InfoHeader = (props) => {
const {population, infected, recovered, deaths, active} = props
return(
//check if props are undefined
!(Object.values(props).every(element => element !== undefined))
?
<div className="infoHeader-landing">
///render this div if props are undefined
</div>
:
<div className="infoHeader-wrapper">
///render this if props are not undefined
</div>
)
}
export default InfoHeader
When a user lands in the page the props will always be undefined until he/she interacts with a section of the map. I basically want to animate infoHeader-landing out once the user clicks on a section of the map.
I have tried setting an animate state on the parent component that renders InfoHeader and sets it to true once the user clicks the map and conditionally give a className to the div I want to animate out. Something like
<div className={props.animate ? "infoHeader-landing-slideOut" : "infoHeader-landing"}/>
I think this is happening because I'm re-rendering the entire component and therefore the animation does not apply but I'm unsure how to reach my goal in this situation, probably using useEffect?.
The animation would be something pretty simple like:
.infoHeader-landing-slideOut {
animation-name: slideUp;
animation-duration: 1s;
}
#keyframes slideUp {
from {
height: 30%;
}
to {
height: 0%;
}
}
Any help would be amazing,
Thanks

Related

My animation is not working when re rendering my react component?

So, my problem is that I have a component, I associated an animation to it and it is working when the component is rendered for the first time, but on an event click I change some conditions and some props associated to this component, But my element is not re rendered, it is just changing what has been changed, that means that the element is not removed from the dom et added to the DOM again, that's why I am not able to see the animation again, so it is not re-rendered or I just did not get what re render means.
I tried some solutions of course, but I am stuck, I tried to use this method :
this.forceUpdate();
But again, I am still not getting anything.
I dont think I have to write the whole code I wrote, becuase it is a lot and includes many other things but This is what I think is needed.
methodWillReceiveProps in my component :
componentWillReceiveProps(props) {
if (props.isRerendered) {
this.forceUpdate();
}
}
props.isRendered is returning true everytime, I checked with some console.log methods.
This is what is rendered :
render() {
return (
<div
className={cs({
"tls-forms": true,
"tls-forms--large": this.props.type === "S",
"tls-forms--medium tls-forms--login": !(this.props.type === "S")
})}
>
// content here
</div>);
}
And here is the sass file and the simple fading animation :
.tls-forms {
animation: formFading 3s;
// childs properties here
}
#keyframes formFading {
0% {
opacity: 0;
}
100% {
opacity: 1;
}
}
I will really appreciate any help given.
You could make use of keys that react is using to determine whether something has changed. This means that your render method should look something like this:
import shortid from "shortid";
getRandomKey = () => {
return shortid.generate();
}
render() {
return (
<div
key={this.getRandomKey()}
className={cs({
"tls-forms": true,
"tls-forms--large": this.props.type === "S",
"tls-forms--medium tls-forms--login": !(this.props.type === "S")
})}
>
// content here
</div>);
}
Since you need to run animation on each render, you'll need to generate some random key every time (that's why we are calling this.getRandomKey() on each render). You can use whatever you like for your getRandomKey implementation, though shortid is pretty good for generating unique keys.
One way of animating a component is to attach a CSS class to it. But, when animation is done, you have to detach the CSS class so that you can re-attach when you want to animate again.
Here is a basic example:
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
animateFlag: false
};
}
componentDidUpdate() {
if (this.state.animateFlag) {
setTimeout(() => {
this.setState({ animateFlag: false });
}, 3000);
}
}
render() {
return (
<div className="App">
<button
onClick={() =>
this.setState({ animateFlag: !this.state.animateFlag })
}
>
{this.state.animateFlag ? "Wait" : "Re-animate"}
</button>
<div className={this.state.animateFlag ? "text animate" : "text"}>
Hello CodeSandbox
</div>
</div>
);
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
.text {
font-size: 40px;
}
.text.animate {
animation: formFading 3s;
}
#keyframes formFading {
0% {
opacity: 0;
}
100% {
opacity: 1;
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root"></div>
Note that, I am setting animateFlag to false in ComponentDidUpdate, so that when I click the Re-animate button again, I can re-attach the animate class to the div element.
I set timeout duration to 3000ms because, the animation takes 3000ms.

How to change React parent component size based on child component size without rendering child component?

I've created a React component which takes any component and renders it as a Pop-up. A parent component receives the component to be rendered (popped up). The rendered component is here the child component which using react-sizeme to get its size and pass back to parent component. The parent component must take the dimensions of child component, so adjusts' its height and width. This is the code:
class Popup extends React.Component<IPopupProps,IComponent>{
constructor(props:IPopupProps){
super(props);
this.state={
childComponent:this.props.children,
style:{
height:0,
width:0
}
}
}
// This function runs two times, before and after rendering child component
// & so have an improper visualization as the size is changed twice here
public OnSize = (size:any) =>{
const width = size.width +20;
const height =size.height+20;
this.setState({
style:{height,
width }
})
}
public render(){
return(
<div className='popup'>
<div style={this.state.style} className='popup-content'>
<a className="close" onClick={this.props.onExit}>
×
</a>
<this.state.childComponent onSize={this.OnSize}/>
</div>
</div>
)
}
}
The initial width and height is set to 0. So it doesn't renders properly. So is there any way so that to hide the child component or avoid its rendering before parent component gets the size?
EDIT: We can't get the size until the child component is rendered. So is there any trick to get this done. Just a component needs to be popped-up properly.
EDIT 2: Here's the PropsBuilder.tsx which calls the Popup.tsx and sends the component to display as children
class PopupBuilder extends React.Component<IPopupBuilderProps, IPopup>{
constructor(props:IPopupBuilderProps){
super(props);
this.state = {
showPopup:false
}
}
public togglePopup = () =>{
this.setState({
showPopup:!this.state.showPopup
})
}
public render (){
return(
<React.Fragment>
<button onClick={this.togglePopup}>{this.props.trigger}</button>
<React.Fragment>
{this.state.showPopup?<Popup onExit={this.togglePopup} >{this.props.component}</Popup>:null}
</React.Fragment>
</React.Fragment>
)
}
}
export default PopupBuilder;
Actually, this looks like more general DOM/JavaScript question.
Consider such case:
const span = document.createElement('span');
span.innerText = 'hello';
span.getBoundingClientRect() // -> { width: 0, height: 0, top: 0, … }
This is an indicator that you don't know the dimensions of the element until it is in DOM (Rendered in react);
document.body.appendChild(span);
span.getBoundingClientRect(); // -> {width: 50, height: 16,  …}
My recommendation to you in this case are:
Child component should accept a property (function) from Parent one
Use React "ref" feature to find actual dimensions of Child element
Call the function in 'componentDidMount' (use componentDidUpdate if child can change dynamically), passing it child component dimensions.
If you don't have access to child component. You may wrap it like this:
// Popup.tsx
class Popup .... {
...
render() {
<Measurer>{this.props.children}</Measurer>
}
}
and implement the logic of fetching dimensions in it. Measurer is a direct child of Popup and their communication can be controlled by you.

adding and removing a class to activate keyframes in react

I am currently working on my first ever react project.
I have placed an onClick event to one of the elements. This element happens to be a button. What I want to achieve is an image going from opacity to 0 in a transition to confirm the user has successfully added an input. This is set-up with the keyframe below
#showTick {
width: 30%;
opacity: 0;
}
.activateKF {
animation: showTick 0.7s;
}
#keyframes showTick {
0% {opacity: 0;}
25% {opacity: 0.5;}
50% {opacity: 1;}
75% {opacity: 0.5;}
100% {opacity: 0;}
}
The showtick styling is what the elements default style is. When the user clicks on the button, I want to add the .activateKF class to the #showTick element. I am doing this with the following code.
goalCreation=()=>{
document.getElementById("showTick").classList.remove("activateKF");
let goal = document.getElementById("enterGoal").value;
if (goal.length < 1){
return false;
} else {
document.getElementById("showTick").classList.add("activateKF");
this.props.submitGoal(goal);
}
}
I am trying to remove the class within the same function so that whenever the user clicks on it, the keyframe can once again be added to the element upon the click event - and the animation can take place. However, what I am finding is that it only works the first time.
Even if I take out the line where the class is removed, it still only works the first time. I can not figure out why?
Please can someone help, so that whenever the user clicks on the button, the keyframe becomes active everytime?
Update: I have included what this actual react component looks like as part of my code
import React, { Component } from 'react';
import '../Styles/creategoal.css';
import specificGoal from '../Images/specificgoal.png';
import cost from '../Images/cost.png';
import tick from '../Images/greentick.jpg';
import '../Styles/creategoal.css';
import '../App.css';
export default class CreateGoal extends Component {
constructor(props){
super(props);
this.state = {
showCostDiv: false,
showSpecificDiv: false
}
}
goalCreation=()=>{
let goal = document.getElementById("enterGoal").value;
if (goal.length < 1){
return false;
} else {
document.getElementById("showTick").classList.add("activateKF");
this.props.submitGoal(goal);
}
}
closeHelp=(e)=>{
let currentClicked = e.target.tagName;
if (this.state.showCostDiv && currentClicked !== "SECTION"){
this.setState({
showCostDiv: false
})
if (this.state.showSpecificDiv && currentClicked !== "SECTION"){
this.setState({
showSpecificDiv: false
})
}
}
}
openSpecificWindow=()=>{
this.setState({
showSpecificDiv: true
})
}
closeSpecificWindow=()=>{
this.setState({
showSpecificDiv: false
})
}
openCostWindow=()=>{
this.setState({
showCostDiv: true
})
}
closeCostWindow=()=>{
this.setState({
showCostDiv: false
})
}
render(){
let specificDivStatus = "hideContent";
let costDivStatus = "hideContent";
if (this.state.showSpecificDiv){
specificDivStatus = "showContent";
}
if (this.state.showCostDiv){
costDivStatus = "showContent";
}
return (
<div onClick={this.closeHelp} className="createGoal">
<div id="banner" className="goalSetBanner">
<h1>SET YOUR GOAL</h1>
</div>
<span className="goalTip">Consider the following when setting your goal:</span>
<section id="BeSpecificHelp" className={specificDivStatus}>
<p>Describe exactly what your goal is, and when its possible use numbers to make it measurable. This excercise will turn your idea or dream
even closer to reality.</p>
<br/>
<p>Examples:</p>
<p><span className="incorrect">Wrong:</span> Weight loss.<br/>
<span className="correct">Right:</span> Losing 8Kg.</p>
<p><span className="incorrect">Wrong:</span> Read more books.<br/>
<span className="correct">Right:</span> Read a new book every 15 days.</p>
<p><span className="incorrect">Wrong:</span> Buying a house.<br/>
<span className="correct">Right:</span> Buying a house within two bedrooms in a given address.</p>
<span id="closeWindowSpecific" onClick={this.closeSpecificWindow}>Close</span>
</section>
<section id="considerCostHelp" className={costDivStatus}>
<p>Do not focus only on the result you will get.</p>
<p><strong>Your time and energy are limited resources</strong></p>
<p>Reflect on what it will take you to achieve this goal.</p>
<p>Finish completing it if you are willing to pay the price.</p>
<span id="closeWindowCost" onClick={this.closeCostWindow}>Close</span>
</section>
<main className="setGoalInfo">
<div id="beSpecificGoal" className="considerGoal">
<img src={specificGoal} alt="Specific Goal" />
<span className="goalHelp">Be as specific as possible</span>
<span id="beSpecificLink" onClick={this.openSpecificWindow} className="link-span">TAP FOR MORE INFO</span>
</div>
<div id="considerCost" className="considerGoal">
<img src={cost} alt="Cost of Goal" />
<span className="goalHelp">What will it cost you?</span>
<span id="considerCost" onClick={this.openCostWindow} className="link-span">TAP FOR MORE INFO</span>
</div>
</main>
<div id="goalAdded">
<img src={tick} id="showTick" alt="Goal Added" />
</div>
<div className="inputDiv">
<input type="text" id="enterGoal" placeholder="What is your goal?"></input>
</div>
<button onClick={this.goalCreation} id="createGoal">CREATE MY GOAL</button>
</div>
)
}
}
Many thanks for the help.
Ground rule with React is that you do not manipulate the DOM directly. React will build a virtual DOM upon rendering and replace only the pieces of the DOM that it detected have changed. If you manipulate the DOM outside the React render cycle, it might not work as you intended.
Neither is it a good idea to use the id attribute on react components. For one, it reduces the re-usability of your components (id's should be unique across a page), and react will also render its own ids in the DOM.
In React, you can use the ref statement which is a function containing either null(upon unmounting) or an element after the item was mounted, however, this one is probably not what you need here (one would rather use that when you read the value from an input).
Probably, you just want to use something like React animation or you just want to add a class depending on a local component state.
From seeing your current monolithic code, you can see that you haven't worked with react all that often yet. You have lots of hard coded data, and lots of repeating concepts.
A way to achieve your current goal, would be to implement something like the following:
const { classNames } = window;
const { Component } = React;
class CheckableButton extends Component {
constructor() {
super();
this.state = {
submitted: false
};
this.handleSubmit = this.handleSubmit.bind( this );
}
componentDidUpdate() {
const { submitted } = this.state;
if (submitted) {
// trigger submitted to be cleared
this.resetTimer = setTimeout( () => this.setState( { submitted: false } ), 700 );
}
}
componentWillUnmount() {
// make sure the state doesn't get manipulated when the component got unmounted
clearTimeout( this.resetTimer );
}
handleSubmit() {
// set the submitted state to true
this.setState( { submitted: true } );
}
render() {
const { submitted } = this.state;
const { title } = this.props;
return (
<button
type="button"
className={ classNames( 'checkable', { 'checked': submitted } ) }
onClick={ this.handleSubmit }>{ title }</button>
);
}
}
ReactDOM.render(
<CheckableButton title="Create goal" />, document.getElementById('container') );
button.checkable {
padding-left: 5px;
padding-top: 5px;
padding-bottom: 5px;
padding-right: 5px;
}
.checkable::before {
display: inline-block;
width: 20px;
content: ' ';
padding-right: 5px;
}
.checkable.checked::before {
content: '✓';
color: darkgreen;
padding-right: 5px;
font-weight: bold;
opacity: 0;
animation: showTick 0.7s;
}
#keyframes showTick {
0% {opacity: 0;}
25% {opacity: 0.5;}
50% {opacity: 1;}
75% {opacity: 0.5;}
100% {opacity: 0;}
}
<script id="react" src="https://cdnjs.cloudflare.com/ajax/libs/react/15.6.2/react.js"></script>
<script id="react-dom" src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/15.6.2/react-dom.js"></script>
<script id="classnames" src="https://cdnjs.cloudflare.com/ajax/libs/classnames/2.2.5/index.js"></script>
<div id="container"></div>
The logic you see in the component, is mainly based on react life cycle events. When the button gets clicked, the state is changed to submitted, this in turn will trigger the componentDidUpdate and there you would be able to check if the submitted flag was set to true. When it did, you can create a callback over setTimeout to remove the submitted flag again.
The handleSubmit function could of course be manipulated to call an eventhandler that was passed down through props
When you redesign your current component, you should probably think about creating components for your "windows", so that they can be manipulated through state / props as well, so they become reusable components

React CSSTransitionGroup not applying classes

I must be missing something. I've looked up various examples for how to do this but I can't get mine to work. I just need to transition one element in or out.
I'm calling togglePopup() to flip the boolean in state, which correctly shows/hides div.popup-msg but the transition classes do not get applied and, obviously, the element doesn't transition in or out.
EDIT: I tried moving the popup in its own component, thinking the issue may have been with it being inside of a stateless functional component instead of directly in render(). Still no luck.
togglePopup = () => {
let isPopupVisible = this.state.isPopupVisible;
isPopupVisible = !isPopupVisible;
this.setState({ isPopupVisible });
};
render() {
const Main = () => {
let removePopup = this.state.isPopupVisible
? <div key={1} className="popup-msg">List has Been Removed</div>
: null;
return (
<div className="main-wrapper">
<CSSTransitionGroup
transitionName="popup"
transitionEnterTimeout={700}
transitionLeaveTimeout={500}>
{removePopup}
</CSSTransitionGroup>
</div>
)
};
return (
<div className="app-wrapper">
<Route exact path="/" component={Main}/>
</div>
)
}
SASS:
.popup-enter {
transition: opacity 700ms ease-in;
opacity: 0.01;
&.popup-enter-active {
opacity: 1;
}
}
.popup-leave {
transition: opacity 500ms ease-in;
opacity: 1;
&.popup-leave-active {
opacity: 0.01;
}
}
Ok, found out that the issue was due to having the popup be inside of another component. So I guess when state got updated the parent component re-rendered and no transition effects were shown. Not sure why exactly that happens since there were no state changes that affected the parent component.
Moving the popup outside the component fixed the issue.

React, Using Refs to scrollIntoView() doesn't work in componentDidUpdate()

I'm using Redux in my app, inside a Component I want to scroll to an specific div tag when a change in the store happens.
I have the Redux part working so it triggers the componentDidUpdate() method (I routed to this compoennt view already).
The problem as far as I can tell, is that the method scrollIntoView() doesn't work properly cos componentDidUpdate() has a default behavior that scrolls to the top overwriting the scrollIntoView().
To work-around it I wrapped the function calling scrollIntoView() in a setTimeout to ensure that happens afeterwards.
What I would like to do is to call a preventDefault() or any other more elegant solution but I can't find where to get the event triggering the 'scrollTop'
I looked through the Doc here: https://facebook.github.io/react/docs/react-component.html#componentdidupdate
and the params passed in this function are componentDidUpdate(prevProps, prevState) ,since there is no event I don't know how to call preventDefault()
I've followd this Docs: https://facebook.github.io/react/docs/refs-and-the-dom.html
And tried different approaches people suggested here: How can I scroll a div to be visible in ReactJS?
Nothing worked though
Here is my code if anyone has any tip for me, thanks
class PhotoContainer extends React.Component {
componentDidUpdate(){
setTimeout(() => {
this.focusDiv();
}, 500);
}
focusDiv(){
var scrolling = this.theDiv;
scrolling.scrollIntoView();
}
render() {
const totalList = [];
for(let i = 0; i < 300; i += 1) {
totalList.push(
<div key={i}>{`hello ${i}`}</div>
);
}
return (
<div >
{totalList}
<div ref={(el) => this.theDiv = el}>this is the div I'm trying to scroll to</div>
</div>
)
};
}
Ok it's been a while but I got it working in another project without the setTimeOut function so I wanted to answer this question.
Since Redux pass the new updates through props, I used the componentWillRecieveProps() method instead of componentDidUpdate() , this allowes you a better control over the updated properties and works as expected with the scrollIntoView() function.
class PhotoContainer extends React.Component {
componentWillReceiveProps(newProps) {
if (
this.props.navigation.sectionSelected !==
newProps.navigation.sectionSelected &&
newProps.navigation.sectionSelected !== ""
) {
this.focusDiv(newProps.navigation.sectionSelected);
}
}
focusDiv(section){
var scrolling = this[section]; //section would be 'theDiv' in this example
scrolling.scrollIntoView({ block: "start", behavior: "smooth" });//corrected typo
}
render() {
const totalList = [];
for(let i = 0; i < 300; i += 1) {
totalList.push(
<div key={i}>{`hello ${i}`}</div>
);
}
return (
<div >
{totalList}
<div ref={(el) => this.theDiv = el}>
this is the div I am trying to scroll to
</div>
</div>
)
};
}
I also struggled with scrolling to the bottom of a list in react that's responding to a change in a redux store and I happened upon this and a few other stackoverflow articles related to scrolling. In case you also land on this question as well there are a few ways this could be a problem. My scenario was that I wanted a 'loading' spinner screen while the list was rendering. Here are a few wrong ways to do this:
When loading = true, render spinner, otherwise render list.
{loading ?
<Spinner />
:
<List />
}
as stated above this doesn't work because the list you might want to scroll to the bottom of isn't rendered yet.
When loading set the display to block for the spinner and none for the list. When done loading, reverse the display.
<div style={{display: loading ? 'block' : 'none'>
<Spinner />
</div>
<div style={{display: loading ? 'none' : 'block'>
<List />
</div>
This doesn't work either since the list you want to scroll to the bottom of isn't actually being displayed likely when you call the scroll.
The better approach for the above scenario is to use a loading that acts as an overlay to the component. This way both the spinner and list are rendered and displayed, the scroll happens, and when the loading is complete, the spinner can be de-rendered or set to be invisible.

Categories