jimmy118
jimmy118

Reputation: 323

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.

Upvotes: 0

Views: 2385

Answers (1)

Icepickle
Icepickle

Reputation: 12796

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

Upvotes: 2

Related Questions