Darkowic
Darkowic

Reputation: 690

Change component props with transition

What I want to achieve is to change opacity with transition animation to get a smooth effect of changing visible elements.

The component I build is Slider. While changing slide opacity of next slide is changing to 1 and to 0 for the current slide.

My first version of Slide component

 export class Slide extends React.PureComponent {
   render() {
    let { img, backgroundColor, active, ...props } = this.props;
    const Wrapper = styled.div`
      background-image: url(${img});
      background-color: ${backgroundColor};
      background-size: cover;
      background-position: 50% 50%;
      opacity: ${active ? 1 : 0};
      transition: opacity 1s;
    `;
    return (
      <Wrapper/>
    )
  }
}

My first thought was to store current slide index in state. So my Slides container know which slide is current and change its active property to true. But this will effect with rerender this Slide and transition is not visible. First question: why?

I ended up with playing with react dom and I change manually opacity of the current element.

export default class Slider extends Component {
  __slidesCount;
  __current;
  __refs = [];

  constructor(props) {
    super(props);
    this.__current = 0;
    this.__slidesCount = this.props.children.length
  }

  componentWillMount() {
    // Save number of slides
  }

  nextSlide = () => {
    let next = this.__current < this.__slidesCount - 1 ? this.__current + 1 : 0,
      nextSlide = this.__refs[next],
      currentSlide = this.__refs[this.__current];

    ReactDOM.findDOMNode(nextSlide).style['opacity'] = 1;
    ReactDOM.findDOMNode(currentSlide).style['opacity'] = 0;
    this.__current = next;
    setTimeout(this.nextSlide, 3000)
  };

  componentDidMount() {
    // set timeout to run it after component mounting is finished
    setTimeout(() => {
      ReactDOM.findDOMNode(this.__refs[0]).style['opacity'] = 1;
    }, 0);
    // call function changing slide to next
    setTimeout(this.nextSlide, 3000)
  }

  render() {
    let { children, ...props } = this.props;
    let slides = children.map((slide, index) => {
      return React.cloneElement(slide, { ref: ref => this.__refs.push(ref) })
    });
    return (
      <SlidesWrapper {...props}>
        {slides}
      </SlidesWrapper>
    )
  }
}

What do you think about this solution? Is any way to do it easier? Of course, I don't want to use jQuery and minimize usage of external css.

Upvotes: 1

Views: 2281

Answers (1)

Irvin Lim
Irvin Lim

Reputation: 2451

EDIT: Turns out that the OP was using Styled Components, and he was creating a new component every time render() is called because const Wrapper... was located inside of the render() method.

This results in the Wrapper component re-mounting every single time the Slide component is updated, and so any CSS transitions will not occur because it is a new instance of a component and hence a new HTML element altogether.


Here's a proof of concept that CSS transitions work even when the active state is passed as a prop, and without external CSS to control the UI state: CodePen. I used the same example of changing opacity and toggling betwen active slides to show that it works.

If your Slides are re-rendering when your state changes, there must be something else that might be re-rendering this entire sequence. Perhaps your components are being removed and re-added again, or something else. You will need to debug this yourself, or provide more code so that we can help you.

Alternatively, as mentioned in the comments, you can make use of ReactCSSTransitionGroup if you find the need to.

Upvotes: 1

Related Questions