简体   繁体   中英

Using conditional timer interval in React componentDidMount() not working

Update

I self answered below, however a more elegant solution that's more "thinking in React" can probably be supplied..


I am creating a React version of this 'animated headline' jQuery widget from CodyHouse: http://codyhouse.co/demo/animated-headlines/index.html (Click 'Type')

So far, so good. I've got the component working so that state changes are managing the className assignments just fine, and the headline ('phrase' in my code) animates in one letter at a time, and cycles through each phrase in order, then resets.

However, what I would like to do is have a 300ms timer interval for when advancing through a letter and have a 2000ms pause when there's a new phrase .

At the moment, despite me trying to set a longer animationDelay state when a new phrase is cycled in (see [0a] in the code below), the timer interval in componentDidMount() never changes. Why is this? How do I get componentDidMount() to use the current changed state.animationDelay?

Note : you can ignore most of the code below. Only the commented lines and componentDidMount() are in question. I'm pasting the rest for completeness.

var React = require('react'),
    ClassNames = require('classnames');

var AnimatedPhrases = React.createClass({

    getInitialProps: function () {

        return {
            animateType: 'type'
        }
    },

    getInitialState: function () {

        return {
            animationDelay:300,
            visible: 0,
            currentLetter: 0,
            currentPhrase: 0,
            phraseCount: this.props.phrases.length
        }
    },

    componentDidMount: function(){
        this.timer = setInterval(this.tick, this.state.animationDelay);
    },

    componentWillUnmount: function(){

        clearInterval(this.timer);
    },

    isLastLetter: function () {

        var phrases = this.props.phrases;
        var currentPhraseLength = phrases[this.state.currentPhrase].name.length;

        return (this.state.currentLetter === currentPhraseLength-1);
    },

    isLastPhrase: function () {

        var phrases = this.props.phrases;
        var currentPhraseLength = phrases[this.state.currentPhrase].name.length;

        return (this.state.currentPhrase === this.state.phraseCount-1 
            && this.state.currentLetter === currentPhraseLength-1);
    },

    tick: function(){

        var phrases = this.props.phrases;
        var currentPhraseLength = phrases[this.state.currentPhrase].name.length;

        // if we are on the last letter of the current phrase, we need
        // to increment the current phrase at the next pass [0] and
        // pause for longer [0a]
        // unless it's the last phrase
        // in which case we reset the current phrase and letter. [1]
        // If we are in the middle of a word, continue to increment 
        // only the current letter [2]
        if (this.isLastPhrase()) {            
            this.setState({
                currentPhrase: 0, // [1]
                currentLetter: 0, // [1]
            });
        }
        if (this.isLastLetter()) {
            this.setState({
                currentLetter: 0, // [0]
                currentPhrase: this.state.currentPhrase+1, // [0]
                animationDelay: 2000 // [0a]
            });
        } else {
            this.setState({
                currentLetter: this.state.currentLetter+1 // [2]
            });
        }
    },

    buildPhrase: function (phrase, index) {

        var isVisible = this.state.currentPhrase == index ? true : false;

        var classes = ClassNames({
            'is-visible': isVisible,
            'is-hidden': !isVisible
        });

        var text = '';

        if ( phrase.hasOwnProperty('name') ) {

            text = phrase.name;

            if (isVisible) {
                // cycle through letters and create an <i> per letter
                // with class depending on matching the current letter
                var splitPhrase = this.singleLetters(text);

                if (phrase.hasOwnProperty('url') && phrase.hasOwnProperty('id')) {
                    return <a className={ classes } key={index} href={phrase.url}>{ splitPhrase }</a>
                } else {
                    return <b className={ classes } key={index}>{ splitPhrase }</b>
                }
            }
        }
    },

    singleLetters: function (phrase, isVisible) {

        var currentLetter = this.state.currentLetter;
        var letters = phrase.split("");
        var letterCount = phrase.length;

        var newLetters = letters.map(function (letter, index) {
            return <i className={ currentLetter >= index ? 'in' : 'out' } key={index}>{letter}</i>
        });

        return newLetters;
    },

    render: function() {

        var buildPhrase =  this.buildPhrase;

        phrases = this.props.phrases.map(function (phrase, index) {
            return buildPhrase(phrase, index);
        });

        var classes = ClassNames('animated-phrase', this.props.animateType);

        return (
            <h1 className="animated-phrase letters type">
                <span>{this.props.headline}</span>
                <span className="word-wrapper waiting">
                    { phrases }
                </span>
            </h1>
        )
    }
});

module.exports = AnimatedPhrases;

OK, so in the end, actually this was just a case of clearing the timer after I set the state in the tick(), as so. If anyone knows a more elegant solution (most likely!) please post :)

tick: function(){

        var phrases = this.props.phrases;
        var currentPhraseLength = phrases[this.state.currentPhrase].name.length;

        // if we are on the last letter of the current phrase, we need
        // to increment the current phrase at the next pass [0]
        // unless it's the last phrase
        // in which case we reset the current phrase and letter [1]
        // if we are in the middle of a word, continue to increment 
        // only the current letter [2]
        if (this.isLastPhrase()) {            
            this.setState({
                currentPhrase: 0, // [1]
                currentLetter: 0, // [1]
            });
        }
        if (this.isLastLetter()) {
            this.setState({
                currentLetter: 0, // [0]
                currentPhrase: this.state.currentPhrase+1, // [0]
                animationDelay: 2000
            });

            // this fixes it!
            clearInterval(this.timer);
            this.componentDidMount();

        } else {
            this.setState({
                currentLetter: this.state.currentLetter+1, // [2],
                animationDelay: 300
            });

            clearInterval(this.timer);
            this.componentDidMount();
        }
    },

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM