reactjs - Using React TransitionGroup, GSAP, and MobX to Re-Render the Same Component -


i have survey-like react application rendering various questions screen using variety of ui components.

however, nature of survey many questions re-render using exact same component. instance, "a / b selector" or "checklist."

what i'd achieve each component, regardless of whether being re-used or mounted dom first time, fade bottom - , fade downward once user selects answer.

here basic example using 5 questions , little box:

import react, {component, proptypes} 'react'; import reactdom 'react-dom';  import { observer, provider, inject } 'mobx-react'; import { observable, computed, action } 'mobx';  import 'gsap'; import transitiongroup 'react-addons-transition-group';  // store class app_store {    @observable showingbox = true;   @observable transduration = .25;    @observable questionindex = 0;    @observable questions = [     {text: 'one'},     {text: 'two'},     {text: 'three'},     {text: 'four'},     {text: 'five'},   ];    @computed currentquestion() {     return this.questions[this.questionindex];   }    @action testtrans () {     this.showingbox = !this.showingbox;     settimeout(() => {       this.questionindex++;       this.showingbox = !this.showingbox;     }, this.transduration * 1000);   }  }  let appstore = new app_store();   // transition w/higher order component function fadesupanddown (component) {   return (     class fadesup extends react.component {       componentwillappear (callback) {         const el = reactdom.finddomnode(this);         tweenmax.fromto(el, appstore.transduration, {y: 100, opacity: 0}, {y: math.random() * -100, opacity: 1, oncomplete: callback});       }        componentwillenter (callback) {         const el = reactdom.finddomnode(this);         tweenmax.fromto(el, appstore.transduration, {y: 100, opacity: 0}, {y: math.random() * -100, opacity: 1, oncomplete: callback});       }        componentwillleave (callback) {         const el = reactdom.finddomnode(this);         tweenmax.to(el, appstore.transduration, {y: 100, opacity: 0, oncomplete: callback});       }        render () {         return <component ref="child" {...this.props} />;       }     }   ) }  // react  @fadesupanddown class box extends react.component {   render () {     return <div classname="box" ref={c => this.container = c}> {this.props.data.text} </div>;   } }   @inject('store') @observer class page extends component {    render () {     const store = this.props.store;      return (       <div classname="page">         <transitiongroup>           { store.showingbox ? <box data={store.currentquestion}/> : null }         </transitiongroup>          <button classname="toggle-btn" onclick={() => { store.testtrans(); } } >           test trans         </button>       </div>     );   }  } 

this works! but... pull off, have manually remove box component dom (in case via showingbox observable), set timeout transition duration, , re-mount component completely.

ultimately guess fine wondering whether in community has come across similar scenario better way of addressing since unmount / remount isn't terribly strong react.

rodrigo's original answer

you try using <transitiongroup>, <transition> tag each component , on tag use onenter , onexit. that's 1 approach several components inside parent component:

<transitiongroup classname="col-12">   <transition     key={props.location.pathname}     timeout={500}     mountonenter={true}     unmountonexit={true}     onenter={node => {       tweenlite.to(node, 0.5, {         autoalpha: 1,         y: math.random() * -100       });     }}     onexit={node => {       tweenlite.to(node, 0.5, {         position: "fixed",         autoalpha: 1,         y: 0       });     }}   /> </transitiongroup>; 

here's example using code, react router. not you're after working sample using approach. go components folder, routes.js file:

https://codesandbox.io/s/mqy3mmznn

the caveat duration set in transition group config, should same of gsap instance in order keep mount/unmount in sync, since onenter , onexit don¡t provide callback.


another option use addendlistener method of <transition> element:

<transition   in={this.props.in}   timeout={duration}   mountonenter={true}   unmountonexit={true}   addendlistener={(n, done) => {     if (this.props.in) {       tweenlite.to(n, 1, {         autoalpha: 1,         x: 0,         ease: back.easeout,         oncomplete: done       });     } else {       tweenlite.to(n, 1, { autoalpha: 0, x: -100, oncomplete: done });     }   }} >   {state =>     <div classname="card" style={{ margintop: "10px", ...defaultstyle }}>       <div classname="card-block">         <h1 classname="text-center">fade in/out component</h1>       </div>     </div>} </transition> 

in case method provide done callback can pass oncomplete handler in angular. in mind, caveat duration in transition group config should longer time in gsap instance, otherwise component unmounted before animation complete. if longer doesn't matter, done callback unmounting you.

here's live sample, go components folder , children.js file:

https://codesandbox.io/s/yvye9nnw


zfalen's derived solution

rodrigo's second suggestion, leveraging <transition /> component react-transition-group (note not same react-addons-transition-group) lead me pretty ideal solution.

by using static value inside of mobx store, can declare single animation duration , derive everywhere else. furthermore, wrapping <transition /> higher-order component function lets me use decorator indicate animation given component should have!

as long pair animation mobx @inject() / <provider /> pattern, can declare gsap transitions separately, tag relevant components needed, , control store.

here's raw code sample (note need have node spun webpack / babel config supports decorators, etc. , little styling make stuff appear.):

import react, {component, proptypes} 'react'; import reactdom 'react-dom';  import { observer, provider, inject } 'mobx-react'; import { observable, computed, action } 'mobx';  require('../../../public/stylesheets/transpage.scss');  import 'gsap'; import transition "react-transition-group/transition";  // lil utility functions const tc = require('../../utils/timeconverter');  // mobx store class app_store {    // toggle & duration transition   @observable showingbox = true;   transduration = .25;    @observable questionindex = 0;    @observable questions = [     {text: 0 },   ];    @computed currentquestion() {     return this.questions[this.questionindex];   }    @action testtrans () {      // toggle component transition out     this.showingbox = !this.showingbox;      // wait until transition out completes     // make changes affect state / props     // transition component in     settimeout(() => {        // in case, add new 'question' survey arbitrarily       this.questions.push({text: this.questionindex + 1 });       this.questionindex++;        this.showingbox = !this.showingbox;     }, tc.tomilliseconds(this.transduration) );   }  }  let appstore = new app_store();   // transition w/higher order component function fadesupanddown (component) {   return (     class fadesup extends react.component {        constructor(props) {         super(props);       }        render () {         const store = this.props.store;          return (           <transition             in={store.showingbox}             timeout={tc.tomilliseconds(store.transduration)}             mountonenter={true}             unmountonexit={true}             addendlistener={(n, done) => {               if (store.showingbox) {                 tweenlite.to(n, store.transduration, {                   opacity: 1,                   y: -25,                   ease: back.easeout,                   oncomplete: done                 });               } else {                 tweenlite.to(n, store.transduration, { opacity: 0, y: 100, oncomplete: done });               }             }}           >             { state => <component {...this.props} /> }           </transition>         )       }     }   ) }  // react stuff  // tag components relevant transition @inject("store") @observer @fadesupanddown class box extends react.component {    constructor(props) {     super(props);   }    render () {     return <div classname="box" > {this.props.data.text} </div>   } }   @inject('store') @observer class page extends component {    render () {     const store = this.props.store;      return (       <div classname="page">         {/* dont need reference transition here */}         {/* works based on derived mobx values */}         <box data={store.currentquestion} />          <button classname="toggle-btn" onclick={() => { store.testtrans(); } } >           test transition         </button>       </div>     );   }  }   reactdom.render(   <provider store={appstore}>     <page />   </provider>,   document.getelementbyid('renderdiv') );  if (module.hot) {   module.hot.accept( () => {     reactdom.render(       <provider store={appstore}>         <page />       </provider>,       document.getelementbyid('renderdiv')     )   }) } 

Comments

Popular posts from this blog

android - InAppBilling registering BroadcastReceiver in AndroidManifest -

python Tkinter Capturing keyboard events save as one single string -

sql server - Why does Linq-to-SQL add unnecessary COUNT()? -