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

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.

Related

Animating conditionally rendered components in React

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

How can I make this element class change on scroll in React?

I want the div element to get the class of "showtext" when you scroll 100 pixels or less above the element. When you're 100 pixels or more above it, it has the class of "hidden".
I am trying to use a ref to access the div element, and use a method called showText to check and see when we scroll to 100 pixels or less above that div element, i'm using scrollTop for this.
Then i use componentDidMount to add a window event listener of scroll, and call my showText method.
I am new to this, so I am sure there is mistakes here and probably bad code. But any help is appreciated!
import React, {Component} from 'react';
class SlideIn extends Component{
state={
showTexts: false,
}
showText=()=>{
const node= this.showTextRef;
if(node.scollTop<=100)
this.setState({
showTexts: true
})
}
componentDidMount(){
window.addEventListener('scroll', this.showText() )
}
render(){
const intro= document.querySelector('.intro')
return(
<div classname={this.state.showTexts ? 'showText' : 'hidden'} ref={node =>this.showTextRef = node}>
{window.addEventListener('scroll', this.showText)}
<h1>You did it!</h1>
</div>
)
}
}
export default SlideIn
I have tried using this.showText in my window scroll event, and as you see above this.showText(), neither have worked. I tried to use the current property on my div ref in my showText method, and it threw a error saying the scrollTop could not define the property of null.
Again I am new to this and have never added a window event listener this way, nor have I ever used scrollTop.
Thanks for any help!
When you attach an event listener you have to pass a function as a parameter. You are calling the function directly when you add the event listener.
In essence, you need to change:
componentDidMount(){
window.addEventListener('scroll', this.showText() )
}
to:
componentDidMount(){
window.addEventListener('scroll', this.showText)
}
In your scroll listener you should check the scroll position of the window(which is the element where you are performing the scroll):
showText = () => {
if (window.scrollY <= 100) {
this.setState({
showTexts: true
});
}
}
Also, you are attaching the event listener in the render method. The render method should only contain logic to render the elements.
Pass function as parameter like
window.addEventListener('scroll', this.showText)
and remove it from return.
Then you just need to do only this in function
if(window.scrollY<=100)
this.setState({
showTexts: true
})
use your div position here
You need to use getBoundingCLientRect() to get scroll position.
window.addEventListener("scroll", this.showText); you need to pass this.showText instead of calling it.
classname has speeling mistake.
showText = () => {
const node = this.showTextRef;
const {
y = 0
} = (node && node.getBoundingClientRect()) || {};
this.setState({
showTexts: y <= 100
});
};
componentDidMount() {
window.addEventListener("scroll", this.showText);
}
render() {
const intro = document.querySelector(".intro");
return (
<div
className={this.state.showTexts ? "showText" : "hidden"}
ref={node => (this.showTextRef = node)}
>
<h1>You did it!</h1>
</div>
);
}
condesandbox of working example: https://codesandbox.io/s/intelligent-shannon-1p6sp
I've put together a working sample for you to reference, here's the link: https://codesandbox.io/embed/summer-forest-cksfh
There are few things to point out here in your code:
componentDidMount(){
window.addEventListener('scroll', this.showText() )
}
Just like mgracia has mentioned, using this.showText() means you're directly calling the function. The right way is just to use this.showText.
In showText function, the idea is you have to get how far user has scrolled from the top position of document. As it was called using:
const top = window.pageYOffset || document.documentElement.scrollTop;
now it's safe to check for your logic and set state according to the value you want, here I have put it like this:
this.setState({
showTexts: top <= 100
})
In your componentDidMount, you have to call showText once to trigger the first time page loading, otherwise when you reload the page it won't trigger the function.
Hope this help
Full code:
class SlideIn extends Component {
state = {
showTexts: false,
}
showText = () => {
// get how many px we've scrolled
const top = window.pageYOffset || document.documentElement.scrollTop;
this.setState({
showTexts: top <= 100
})
}
componentDidMount() {
window.addEventListener('scroll', this.showText)
this.showText();
}
render() {
return (
<div className={`box ${this.state.showTexts ? 'visible' : 'hidden'}`}
ref={node => this.showTextRef = node}>
<h1>You did it!</h1>
</div>
)
}
}
.App {
font-family: sans-serif;
text-align: center;
height: 2500px;
}
.box {
width: 100px;
height: 100px;
background: blue;
position: fixed;
left: 10px;
top: 10px;
z-index: 10;
}
.visible {
display: block;
}
.hidden {
display: none;
}

How to return an HTML <div> tag from a javascript function in React?

I am working on a React application where I am trying to render text on the screen when a button is clicked. I have defined a function onButtonClick which gets triggered whenever the button is clicked. However, the HTML that I am returning from the function is not rendered on the screen. I am in the learning stages of React so please excuse me if the question seems silly.
class App extends Component {
constructor() {
super();
this.state = {
blockno:0
}
}
OnButtonClick = () => {
this.setState({blockno: this.state.blockno + 1})
return(
<div>
<h3>Some text</h3>
</div>
);
}
render() {
return(
<div>
<Button onButtonClick={this.OnButtonClick}/>
</div>
);
}
}
The value is being returned, but the framework/browser/etc. has no reason to do anything with that value.
Try thinking about this a different way, a "more React way". You don't want to return the value to be rendered, you want to update state. Something like this:
constructor() {
super();
this.state = {
blockno:0,
showDiv: false // <-- note the new property in state
}
}
OnButtonClick = () => {
this.setState({blockno: this.state.blockno + 1, showDiv: true})
}
Now you're not returning anything, but rather updating the state of the component. Then in your render method you conditionally render the UI based on the current state:
render() {
return(
<div>
<Button onButtonClick={this.OnButtonClick}/>
{
this.state.showDiv
?
<div>
<h3>Some text</h3>
</div>
: ''
}
</div>
);
}
The click handler doesn't modify the page, it just modifies the state of the component you're writing. The render method is responsible for rendering the UI based on that state. Any time state changes, render will be called again to re-render the output.
(Note: It's not 100% clear if this is exactly the functionality you're looking for in the UI, since it's not really clear what you're trying to build. But the point here is to illustrate how to update state and render output in React. Your logic can be tweaked as needed from there.)
You have to make a render based on your state. Please check the tutorial at the react docs to learn more about how React works. It's really good
Here is a version of your code that works. Hope it helps
class App extends Component {
constructor() {
super();
this.state = {
blockno: 0
};
}
OnButtonClick = () => {
//updates the states
this.setState({ blockno: this.state.blockno + 1 });
};
//remember: every time there is an update to the state the render functions re-runs
render() {
//variable holding the blocks in an array
let blocks = []
//if blockno is greater than 0, it checks everytime that there is a state change
if (this.state.blockno > 0) {
//for every block added
for (let index = 0; index < this.state.blockno; index++) {
//We`re going to add to the array of blocks a new div with the block number
blocks.push(
<div>
<h3>My block number is {index}</h3>
</div>
);
}
}
return (
<div>
<div>
{/**button that updates the state on every click */}
<button onClick={this.OnButtonClick}>
Click me to add a new div!
</button>
</div>
{/**This render the blocks variable that holds the divs */}
{blocks}
</div>
);
}
}
What I see is that you are trying to build a counter. The value that you're returning from the click handler function can't be rendered, instead you need to manage it in the render function as follow:
class App extends Component {
constructor() {
super();
this.state = {
blockno: 0
}
}
OnButtonClick = () => {
this.setState(prevState => ({ blockno: prevState.blockno + 1 }));
}
render() {
return(
<div>
{this.state.blockno > 0 && <div>some text {this.state.blockno}</div>}
<Button onButtonClick={this.OnButtonClick} />
</div>
);
}
}
Also note that the setState method is asynchronous, please read the documentation https://reactjs.org/docs/react-component.html#setstate

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.

Categories