0

I have a short quiz app and am trying to apply some animations to it. My idea is that only one question will be displayed at a time, with transitions between each one. I used create-react-app as boilerplate, and I'm using react-transitions-group v1 to apply said transitions. Right now my root component looks like this:

import React, { Component } from 'react';
import logo from './logo.svg';
import './App.css';
import Panel from './components/Panel';
import Results from './components/Results';
import Intro from './components/Intro';
import Form from './components/Form';

class App extends Component {

  constructor (props) {
    super (props);
    this.state = {
      collaborator: 0,
      pilot: 0,
      producer: 0,
      harmonizer: 0,
      counter: 0,
      questions: [
       'Where do you put the most effort on a day-to-day basis?',
       'What are your biggest blind spots?',
       'In what settings do you thrive?',
       'In what settings do you struggle?'
      ],
      answers: [
        [
          {answer: 'Team building', archetype: 'collaborator'},
          {answer: 'Directing strategy', archetype: 'pilot'},
          {answer: 'Driving task completion', archetype: 'producer'},
          {answer: 'Keeping processes and relationships running smoothly', archetype: 'harmonizer'}
        ],
        [
          {answer: 'Setting a clear direction and creating a personal brand', archetype: 'collaborator'},
          {answer: 'Making space for others and planning for the longterm', archetype: 'pilot'},
          {answer: 'Connecting with team members and innovating', archetype: 'producer'},
          {answer: 'Accepting ambiguity and addressing conflict', archetype: 'harmonizer'}
        ],
        [
          {answer: 'Settings where team members are seeking coaching and development', archetype: 'collaborator'},
          {answer: 'Ambiguous and high growth environments', archetype: 'pilot'},
          {answer: 'Organizations with clear structures and processes', archetype: 'producer'},
          {answer: 'Volatile settings that need to be tamed', archetype: 'harmonizer'}
        ],
        [
          {answer: 'Settings where unilateral decision making is required', archetype: 'collaborator'},
          {answer: 'Conservative environments that discourage innovation', archetype: 'pilot'},
          {answer: 'Teams where each member desires independence', archetype: 'producer'},
          {answer: 'Anywhere tough feedback needs to be given', archetype: 'harmonizer'}
        ]
      ]
    }
    this.onSelect = this.onSelect.bind(this);
    this.onSelectIntro = this.onSelectIntro.bind(this);
  }

  onSelect(value) {
    this.setState(prevState => {
      return {
        [value]: prevState[value] + 1,
        counter: prevState.counter + 1
      }
    })
  }

  onSelectIntro() {
    this.setState(prevState => {
      return { counter: prevState.counter + 1 };
    })
  }

  render() {
    switch (this.state.counter) {
      case 0:
        return <Intro onClick={this.onSelectIntro}/>;
        break;
      case 1: case 2: case 3: case 4:
         return <Panel 
          question={this.state.questions[this.state.counter - 1]}
          answers={this.state.answers[this.state.counter - 1]}
          onSelect={this.onSelect}
        />;
        break;
      case 5:
        return <Results />;
        break;
    }
  }
}

export default App;

My Panel component is Looks like this:

import React from 'react';
import Form from './Form';
import PropTypes from 'prop-types';

function PanelOne (props) {
    return (
        <div>
            <Form question={props.question} answers={props.answers} onSelect={props.onSelect}/>
        </div>
    )
}

PanelOne.propTypes = {
  answers: PropTypes.array.isRequired,
  onSelect: PropTypes.func.isRequired
};

export default PanelOne;

Form is where my transitions are applied, and it looks like this:

import React from 'react';
import PropTypes from 'prop-types';
import { RadioGroup, RadioButton } from 'react-radio-buttons';
import { CSSTransitionGroup } from 'react-transition-group';

function Form (props) {
    return (
        <div>
            <CSSTransitionGroup 
                transitionName='slide'
                transitionEnterTimeout={300}
                transitionLeaveTimeout={300}
            >
                <h3>{props.question}</h3>
                <RadioGroup onChange={props.onSelect}>
                    {
                        props.answers.map(answer => 
                            <RadioButton key={answer.answer} value={answer.archetype}>
                                {answer.answer}
                            </RadioButton>
                        )
                    }
                </RadioGroup>
            </CSSTransitionGroup>
        </div>
    )
}

Form.propTypes = {
  answers: PropTypes.array.isRequired,
  onSelect: PropTypes.func.isRequired
};

export default Form;

Lastly, here's my index.css file:

body {
  margin: 0;
  padding: 0;
  font-family: sans-serif;
}

.slide-enter {
  opacity: 0.01;
}
.slide-enter.slide-enter-active {
  opacity: 1;
  transition: opacity 500ms ease-in;
}
.slide-leave {
  opacity: 1;
}
.slide-leave.slide-leave-active {
  opacity: 0.01;
  transition: opacity 300ms ease-in;
}

I expect that when I click on an answer to a question, the next question will be displayed after the transition is applied. However, when I click an answer what actually happens is that the next question is displayed immediately, with no transition between the two questions. I really am at a loss for what I'm doing wrong here...

bkula
  • 541
  • 3
  • 10
  • 22

1 Answers1

0

Your Form and Panel components are functional components, so they do not have instance.

React Components, Elements, and Instances

Only components declared as classes have instances, and you never create them directly: React does that for you.

So when their props change, the underlying DOM tree might be remove. This way leave animations doesn't happen.

Reconciliation

When a component updates, the instance stays the same, so that state is maintained across renders.

You should try with Class components.

  • yup, sorry, first time i use this website... Can i still change this? – Progragamer Jan 12 '18 at 17:24
  • I know @K.Davis is correct, but I want to say that I really appreciate this answer nonetheless. My original question has been edited to include my `Panel` component. – bkula Jan 12 '18 at 18:26
  • @Progragamer I've changed my `Panel` and `Form` components to be class components instead of functional components (thank you for those blog posts!). I've changed nothing else in my code. Unfortunately, I'm getting the same result as before - the animation is not taking place. – bkula Jan 12 '18 at 21:02
  • Can you log componentWillUnmount and componentDidMount in Form component? – Progragamer Jan 12 '18 at 21:10
  • @Progragamer yeah I just added those. Each time the state changes/the thing rerenders, `componentDidMount` logs properly. However, `componentWillUnmount` doesn't log at all until `state.counter` reaches 5 and `Panel` no longer renders and is replaced by `Result`. – bkula Jan 12 '18 at 21:17
  • This is what we expect, both of them should log only once. So this is not a lifecycle issue. – Progragamer Jan 12 '18 at 21:19
  • @Progragamer any other ideas for what might be going on?? – bkula Jan 12 '18 at 21:27
  • why there is no import for your css in your Form file? it seems to be a problem – Progragamer Jan 12 '18 at 21:34
  • @Progragamer Just tried adding that import, and it didn't make a difference. I don't think I need to import it anyway since that's hooked up to my app out of the box with `create-react-app`. I tested that by changing the background color in `index.css` to black, and it worked fine. – bkula Jan 12 '18 at 21:41
  • well, i've no more idea, sorry mate. – Progragamer Jan 12 '18 at 21:42