Salut à tous :)

Voilà je suis entrain de me faire la main sur ReactJS, et je rencontre un petit souci avec le plugin react-transition-group permettant d'animer des éléments d'une liste.
J'ai scrupuleusement suivi la doc, mais pas moyen d'avoir un pet d'animation, rien, nada, quedal, niet !

Voici le code du composant qui gère la liste d'éléments (rien de bien sorcier ici) :

import React, { Component } from 'react'
import Item from './Item'
import CSSTransitionGroup from 'react-transition-group/CSSTransitionGroup'

class List extends Component {

    constructor (props) {
        super(props)
    }

    render () {
        return (
            <div className="block list">
                <CSSTransitionGroup
                    transitionName="slide-left"
                    transitionEnterTimeout={800}
                    transitionLeaveTimeout={500}>
                    {this.props.list.map((item, index) => <Item item={item} key={index} />)}
                </CSSTransitionGroup>
            </div>
        )
    }

}

export default List

Et voici le CSS (au cas où je me sois gourré quelque part) :

.slide-left-enter { transform: translateX(-20px) }
.slide-left-enter.slide-left-enter-active {
    transform: translateX(0);
    transition: transform 800ms ease-in;
}
.slide-left-leave { transform: translateX(0) }
.slide-left-leave.slide-left-leave-active {
    transform: translateX(-20px);
    transition: transform 500ms ease-in;
}

Donc si quelqu'un a une idée, la solution (ou un chèque de 1500€, sait-on jamais, sur un mal-entendu..) je suis prenneur :)

Merci !

6 réponses


PhiSyX
Réponse acceptée

Je ne connais pas trop le système d'animation de react mais en tout cas c'est mal fichus :D lol (ou c'est juste moi qui ne sait pas l'utiliser ;-D)

J'ai fait ceci:
List.js:

//...
{this.props.items.map((item, index) => (<Item item={item} delay={index === 0 ? 100 : (index * 100)} key={index}/>))}
//

Item.js:

//...
<CSSTransitionGroup
        transitionName="slide-left"
        transitionAppear={true}
        transitionAppearTimeout={delay}
        transitionEnter={false} 
        transitionLeave={false}>
    <div className="item">{item.id}</div>
</CSSTransitionGroup>
//...

CSS:

.item {
    transition: transform .5s ease-in, opacity .5s;
}
.slide-left-appear {
    opacity: 0;
    transform: translateX(-20px);
}

Les props transitionEnter et transitionLeave sont à false puisque de toute manière le système n'a pas l'air de les utiliser. Pq? No sé.
(Donc les classes css .slide-left-enter/leave/+-active, ils ne les utilisent pas...)

Par contre t'auras un problème de delai (qui sera trop long si bcp d'éléments) lors de nouveaux éléments sur this.props.items x)

Hum... Chez moi, ça fonctionne: quand on ajoute ou supprime des éléments du tableau.
Par contre, si tu veux que l'animation se fasse dès le départ, il te faut ajouter la prop transitionAppear={true} (& transitionAppearTimeout) au composant CSSTransitionGroup. https://facebook.github.io/react/docs/animation.html#animate-initial-mounting

betaWeb
Auteur

@]PhiSyX merci pour ta réponse, j'ai effectivement exploré la solution de l'ajout de props transitionAppear={true} et transitionAppearTimeout, mais ça n'a rien changé :/
Pourtant mes items ont bien une prop key.
Alors à savoir que cette liste s'affiche après un appel async, peut-être que ça change quelque chose sur la façon d'implémenter les animations je ne sais pas ?

Après un appel async, càd?
Non bah normalement tout devrait fonctionner correctement...

https://www.dropbox.com/s/57383uivtzsoagl/26804.gif?dl=0]

Ce que j'ai fait:
App.js: (codes sommaires)

class App extends React.Component {
  constructor (props) {
    super(props)

    this.state = { items: [] }
  }

  componentDidMount () {
    this.items = xhr.get('/test.json')
      .then((result) => {
        this.setState({ items: result.data })
      })
  }

  render () {
    return (
      <div className="l-app">
        <div className="l-view-column">
          <List items={this.state.items}/>

          <hr/>

          {
            // Après un appel async?
            // Les propriétés `transitionAppear` et `transitionAppearTimeout` sont indispensables dans ce cas-ci...
            this.state.items.length > 0 &&
            <List items={this.state.items}/>
          }
        </div>
      </div>
    )
  }
}

List.js:

class List extends React.Component {
  render() {
    return (
      <div style={{'overflow': 'auto'}}>
        {
          this.props.items.length === 0 &&
          <i className="fa fa-spinner fa-spin fa-fw"></i>
        }

        <CSSTransitionGroup
          transitionName="slide-left"
          transitionAppear={true}
          transitionAppearTimeout={800}
          transitionEnterTimeout={800}
          transitionLeaveTimeout={300}>
          {
            this.props.items.map((item, i) => (
              <Item item={item} key={i}/>)
            )
          }
        </CSSTransitionGroup>
      </div>
    )
  }
}

Item.js:

class Item extends React.Component {
  render() {
    return (
      <div>
        {this.props.item.id}
      </div>
    )
  }
}

CSS: (ajout des selecteurs pour l'appear)

.slide-left-enter { transform: translateX(-20px) }
.slide-left-appear.slide-left-appear-active,
.slide-left-enter.slide-left-enter-active {
    transform: translateX(0);
    transition: transform 800ms ease-in;
}
.slide-left-leave { transform: translateX(0) }
.slide-left-appear,
.slide-left-leave.slide-left-leave-active {
    transform: translateX(-20px);
    transition: transform 500ms ease-in;
}
betaWeb
Auteur

Merci pour ton exemple, j'ai déporté le composant d'animation sur l'Item et là j'ai une animation, sauf que tous les éléments arrivent en même temps et ce n'est pas ce que je souhaite. J'aimerais qu'ils apparaîssent les uns après les autres. Donc ce que j'ai fait, c'est que j'ai utilisé l'attribut transitionEnterTimeout en lui appliquant une valeur dynamique qui sert de délai (index * 100). Et là, oh surprise, là où ça donnerait le résultat attendu, je n'ai pas d'effet n'animation sur chaque élément avec un délai mais juste un comportement bizarre, random qui anime juste le premier élément.

Bref c'est pas encore ça qui est ça...

Voici le code mis à jour :

/** List **/
import React, { Component } from 'react'
import Item from './Item'

class List extends Component {

    constructor (props) {
        super(props)
    }

    render () {
        if (!this.props.list || !this.props.list.length) return <p>Aucun élément dans la liste</p>
        return <div className="block list">{this.props.list.map((item, index) => <Item item={item} delay={index * 100} key={index} />)}</div>
    }

}

export default List
/** Item **/
import React from 'react'
import CSSTransitionGroup from 'react-transition-group/CSSTransitionGroup'

const Item = ({ item, delay }) => {
    return (
        <CSSTransitionGroup
            transitionName="slide-left"
            transitionAppear={true}
            transitionAppearTimeout={800}
            transitionEnterTimeout={delay}
            transitionLeaveTimeout={0}>
            <div className="item">{item.weather[0].description}</div>
        </CSSTransitionGroup>
    )
}

export default Item
/** CSS **/
.slide-left-appear,
.slide-left-enter {
    opacity: 0;
    transform: translateX(-20px);
}
.slide-left-appear.slide-left-appear-active,
.slide-left-enter.slide-left-enter-active {
    opacity: 1;
    transform: translateX(0);
    transition: transform .5s ease-in, opacity .5s;
}
.slide-left-leave {
    transform: translateX(0);
}
.slide-left-leave.slide-left-leave-active {
    opacity: 0;
    transform: translateX(-20px);
    transition: transform .3s ease-in, opacity .3s;
}

Alors il est possible que je me sois mélangé les pinceaux sur la déclaration du CSS. Si tu as une idée.. :)

Concernant ton interrogation sur ma partie async, c'est tout simplement de la récupération d'infos via une API, donc de l'asynchrone.
Cf le code suivant :

/** Fetch des data depuis l'API **/
async fetch (e) {
  if ((e && e.which !== 13) || this.state.query === '') return false;
  const currentCity = this.getCity()
  if (currentCity !== null && this.state.query === currentCity) return false;
  this.setState({ results: null, loading: true })
  const url = api.url(this.state.query)
  let response = await fetch(url)
  if (response.status === 200) {
    let results = await response.json()
    this.setState({ results, query: results.city.name })
  }
  this.setState({ loading: false })
}
betaWeb
Auteur

Merci pour ta réponse, j'essaye ça ce soir.

Après je suis d'accord que ce système d'animation est vraiment très mal foutu ! Et la doc n'est pas plus claire ^^'
Concernant les props transitionEnter et transitionLeave : je ne suis pas fou alors ! Chez toi aussi elles ne sont pas utilisées. Donc ouais c'est pas génial mais bon faut composer avec quoi, pas trop le choix ^^'

C'est une liste qui ne pourra pas avoir plus de 5 items, et il n'y a pas vocation à en rajouter donc de ce côté là c'est bon, mais bien vu, il faudra que je fasse gaffe sur d'autres projets ;)