Stan Luo
Stan Luo

Reputation: 3889

Using ReactCSSTransitionGroup with styled-component

I'm using styled-components instead of tradition way of css. But I don't know how it can work together with ReactCSSTransitionGroup.

Basically, ReactCSSTransitionGroup looks for certain classnames in css resource, then apply to a component throughout its lifecycle. However, with styled-components, there are not any class names, styles are applied to components directly.

I know I can choose not to use ReactCSSTransitionGroup because the two technique doesn't look compatible. But when I use only styled-components, seems I can't render any animation when a component is unmounted - it's pure css, can't access component's lifecycle.

Any help or recommendation is appreciated.

Upvotes: 35

Views: 25267

Answers (6)

SmileyJames
SmileyJames

Reputation: 117

There is a great blog post explaining how to do this: https://dev.to/terrierscript/styled-component--react-transition-group--very-simple-transition-jja

They use a low level Transiton component available from react-transition-group: http://reactcommunity.org/react-transition-group/transition

// This is overly simplified, but styles change depend on state from Transition
const MyStyledComponent = styled.div`
  transform: translateY(${({ state }) => (state === 'exited' ? "0" : "-100%")});
  transition: transform 2s;
`

const App = () =>
  <Transition in={animate} timeout={500}>
    {(state) => (
      // state change: exited -> entering -> entered -> exiting -> exited
      <MyStyledComponent state={state}>Hello</MyStyledComponent>
    )}
  </Transition>

Upvotes: 1

Mike Goatly
Mike Goatly

Reputation: 7558

I didn't want to use injectGlobal as suggested in another answer because I needed to make the transitions different per component.

It turns out to be pretty easy - just nest the transition classes in the styling for the component:

import React from "react";
import CSSTransitionGroup from 'react-transition-group/CSSTransitionGroup';
import styled from 'styled-components';

const appearDuration = 500;
const transitionName = `example`;

const Container = styled.section`
        font-size: 1.5em;
        padding: 0;
        margin: 0;

        &.${transitionName}-appear {
            opacity: 0.01;
        }

        &.${transitionName}-appear-active {
            opacity: 1;
            transition: opacity ${appearDuration}ms ease-out;
        }`;

export default () => {

    return (
        <CSSTransitionGroup
            transitionName={transitionName}
            transitionAppear={true}
            transitionAppearTimeout={appearDuration}>
            <Container>
                This will have the appear transition applied!
            </Container>
        </CSSTransitionGroup>
    );
};

Note that I'm using the newer CSSTransitionGroup, rather than ReactCSSTransitionGroup, but it should work for that too.

Upvotes: 52

Guilherme Rocha
Guilherme Rocha

Reputation: 1

import React from "react";
import { CSSTransition } from 'react-transition-group';

const styles = theme => ({
    'fade-enter':{
        opacity: 0,
    },
    'fade-enter-active':{
        opacity: 1,
        transition: "opacity 300ms"
    },
    'fade-exit':{
        opacity: 1,
    },
    'fade-exit-active':{
        opacity: 0,
        transition: "opacity 300ms"
    },
})

class myAnimatedComponent extends React.Component {

  constructor(props){
    super(props);
  }

  render(){

    let {classes} = this.props;

    return (
        <CSSTransition
            in={this.props.conditionVariable}
            classNames={{
               enter: classes['fade-enter'],
               enterActive: classes['fade-enter-active'],
               exit: classes['fade-exit'],
               exitActive: classes['fade-exit-active'],
            }}
            timeout={300}
            unmountOnExit>
                <span>This will have the transition applied to it!</span>
        </CSSTransition>
    );
  }
};

export default (styles)(myAnimatedComponent);

I had to use classes['fade-enter'] etc, because React changes the name of all classes in this component due to the fact that I used withStyles. And because of that too, when I export the component, React inserts my classes into this component's props, that's why I also had to create a variable called classes to catch those classes.

Upvotes: -1

Daniel Apt
Daniel Apt

Reputation: 2648

Mike Goatly's approach is great, but I had to make small changes to make it work. I changed the <CSSTransition>'s props, and used a function as its child.

See below for an example of a component, which fades in/out based on a state change:

import React, { Component } from "react";
import ReactDOM from "react-dom";
import { CSSTransition } from "react-transition-group";
import styled from "styled-components";

const Box = styled.div`
  width: 300px;
  height: 300px;
  background: red;
  transition: opacity 0.3s;

  // enter from
  &.fade-enter {
    opacity: 0;
  }

  // enter to
  &.fade-enter-active {
    opacity: 1;
  }

  // exit from
  &.fade-exit {
    opacity: 1;
  }

  // exit to 
  &.fade-exit-active {
    opacity: 0;
  }
}`;

export default class App extends Component {
  constructor() {
    super();
    this.state = {
      active: true
    };

    setInterval(() => this.setState({ active: !this.state.active }), 1000);
  }

  render() {
    return (
      <CSSTransition
        in={this.state.active}
        classNames="fade"
        timeout={300}
        unmountOnExit
      >
        {() => <Box />}
      </CSSTransition>
    );
  }
}

Upvotes: 14

Chiahao Lin
Chiahao Lin

Reputation: 31

You can use css variable selector in styled-components. Like this:

const Animation = styled(ReactCSSTransitionGroup)`
  ${({ transitionName }) => `.${transitionName}-enter`} {
    opacity: 0;
  }

  ${({transitionName}) => `.${transitionName}-leave`} {
    opacity: 1;
  }
`

const animationID = 'some-hashed-text'

const AnimationComponent = props => (
  <Animation
    transitionName={animationID}
    transitionEnterTimeout={0.1}
    transitionLeaveTimeout={2000}
  >
    <div>some content</div>
  </Animation>
)

Upvotes: 3

AxeEffect
AxeEffect

Reputation: 7501

Use the injectGlobal() styled-component helper method where your React app is bootstrapped. With this method you can style any CSS selector as if you'd be using conventional CSS.

First create a JS file exporting a template literal with your CSS for the react-transition-group (please not I'm using v2.1 new class names syntax):

globalCss.js

const globalCss = `
    .transition-classes {
        /* The double class name is to add more specifity */
        /* so that this CSS has preference over the component one. */
        /* Try removing it, you may not need it if properties don't collide */
        /* https://www.styled-components.com/docs/advanced#issues-with-specificity */

        &-enter&-enter {
        }

        &-enter&-enter-active {
        }

        &-exit&-exit {
        }

        &-exit&-exit-active {
        }
    }
`;

export default globalCss;

Then on your entry point file:

index.jsx

import { injectGlobal } from "styled-components";
import globalCss from "./globalCss.js";

injectGlobal`${ globalCss }`; // <-- This will do the trick

ReactDOM.render(
    <Provider store={ Store } >
        <HashRouter >
            <Route path="/" component={ Component1 } />
            <Route path="/" component={ Component2 } />
        </HashRouter>
    </Provider>,
    document.getElementsByClassName("react-app")[0]
);

However, if you just use CSS/SASS/Less to write the classes for the react-trasition-group even when you use styled-components, it also works well.

Upvotes: 2

Related Questions