P J
P J

Reputation: 70

How to trigger animation using react-transition-group

The problem is I have a form with three states: error, info, and success. Depending on there a response from server am firing toaster using above states I need to add a fade in-out animation when a response from the server is available.

toasterService.js

 import React, {useState} from 'react';
 import {Transition} from 'react-transition-group';
 import './toasterService.css'

 export default function ToasterService(content, timeout, style) {
const inProp = useState(true); // always call hook on top level
const duration = timeout;
const transitionStyles = {
    entering: {opacity: 1},
    entered: {opacity: 1},
    exiting: {opacity: 0},
    exited: {opacity: 0},
};

let defaultStyle = {};

switch (style) {
    case 'info' :
        defaultStyle = {
            transition: `opacity ${duration}ms ease-in-out`,
            opacity: 0,
            backgroundColor: '#00c5dc',
            color: '#ffffff'
        };
        break;

    case 'success' :
        defaultStyle = {
            transition: `opacity ${duration}ms ease-in-out`,
            opacity: 0,
            backgroundColor: '#8ebe4b',
            color: '#ffffff'
        };
        break;
    case 'danger' :
        defaultStyle = {
            transition: `opacity ${duration}ms ease-in-out`,
            opacity: 0,
            backgroundColor: '#FF0000',
            color: '#ffffff'
        };
        break;
    default :
}


return (<div className="main-alert">

        <Transition in={inProp} timeout={duration}>
            {state => (
                <div style={{
                    ...defaultStyle,
                    ...transitionStyles[state]
                }}>
                    {content}
                </div>
            )}
        </Transition>
    </div>

);
}

Login.js

import ToastService from '../../services/core/toasterService';
// on click of login btn
  socialSignIn = () => {
    let obj = {};
    obj = this.state;

    fetch(url,
        {
            method: 'post',
            body: JSON.stringify(obj)

        }).then(function (res) {
        console.log(res.json());
          ToastService('success', 5000,'info');
        return res.json();

    })
};

Toast Service receiving 3 arguments but the toaster is not appearing. What I am missing?

Upvotes: 3

Views: 2394

Answers (1)

cbdeveloper
cbdeveloper

Reputation: 31335

I recently built a Toastr component myself, with basic functionality using styled-components, animejs and react-transition-group that might help you to get it right.

Note: I think it's easier to use animejs rather than setting styles for each phase of the transition. You basically get the reference for the entering or exiting element and animate it how you like using animejs.

react-transition-group will give you a reference to the element from these props:

<Transition
  key={item.id}
  onEntering={animateEnter}  // animateEnter will have a reference to the element
  onExiting={animateExit} // animateExist will have a reference to the element
  timeout={{
    enter: 500,
    exit: 500
  }}
  unmountOnExit={true} // I was testing, but I don't think this prop is necessary in my component
>

See working example on CodeSandbox

enter image description here

This is the code:

index.js

import React from "react";
import ReactDOM from "react-dom";
import Toastr from "./Toastr";
import SomeComponent from "./SomeComponent";

import "./styles.css";

function App() {
  return (
    <Toastr>
      <SomeComponent />
    </Toastr>
  );
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

Toastr.js

import React, { useRef, useEffect, useState } from "react";
import styled from "styled-components";
import { TransitionGroup, Transition } from "react-transition-group";
import anime from "animejs";
import ToastrContext from "./ToastrContext";

// CREATE A USE TOASTER HOOK ?
// MAYBE CREATE AN APP STATE TO STORE THE TOASTS

const S = {};

S.FixedContainer = styled.div`
  position: fixed;
  bottom: 10px;
  /* right: 5px; */
  /* left: 0; right: 0; */
  /* CENTER IT HORIZONTALLY */
  left: 50%;
  transform: translateX(-50%);
`;

S.ToastContainer = styled.div`
  width: 300px;
  height: 64px;
  margin-top: 10px;
  margin-bottom: 10px;
  /* padding-left: 10px; */
  color: white;
  font-weight: bold;
  background: #39c16c;
  border-radius: 5px;
  display: flex;
  align-items: center;
  justify-content: center;
`;

function Toastr(props) {
  const lastToastLengthRef = useRef(0);
  const [toasts, setToasts] = useState([]);
  const toastID = useRef(0);

  console.log("Toastr rendering...");
  console.log(toasts);

  function addNewToast(toast) {
    setToasts(prevState => {
      const aux = Array.from(prevState);
      aux.push({ msg: toast, id: toastID.current });
      toastID.current = toastID.current + 1;
      return aux;
    });
  }

  useEffect(() => {
    if (toasts.length > lastToastLengthRef.current) {
      console.log("useEffect: Toast was added...");
      // TOAST WAS ADDED
      setTimeout(() => {
        setToasts(prevState => {
          const aux = Array.from(prevState);
          aux.shift();
          return aux;
        });
      }, 1000);
      lastToastLengthRef.current = toasts.length;
      return;
    }
    lastToastLengthRef.current = toasts.length;
  }, [toasts]);

  function animateEnter(element) {
    anime({
      targets: element,
      opacity: 0,
      duration: 0
    });
    anime({
      targets: element,
      opacity: 1,
      easing: "easeOutExpo",
      duration: 2000
    });
  }

  function animateExit(element) {
    anime({
      targets: element,
      opacity: 0,
      easing: "easeOutExpo",
      duration: 2000
    });
  }

  // const toastItems = toasts.map((item,index) =>
  //   <S.ToastContainer key={item.id}>{item.msg}</S.ToastContainer>
  // );

  const toastItems = toasts.map((item, index) => (
    <Transition
      key={item.id}
      onEntering={animateEnter}
      onExiting={animateExit}
      timeout={{
        enter: 500,
        exit: 500
      }}
      unmountOnExit={true}
    >
      <S.ToastContainer>{item.msg}</S.ToastContainer>
    </Transition>
  ));

  return (
    <React.Fragment>
      <S.FixedContainer>
        <TransitionGroup component={null}>{toastItems}</TransitionGroup>
        {/* {toastItems} */}
      </S.FixedContainer>
      <ToastrContext.Provider value={addNewToast}>
        {props.children}
      </ToastrContext.Provider>
    </React.Fragment>
  );
}

// Toastr.whyDidYouRender = true;

export default Toastr;

ToastrContext.js

import React from "react";

const ToastrContext = React.createContext(null);

export default ToastrContext;

SomeComponent.js (will emit toasts)

import React, { useContext } from "react";
import ToastrContext from "./ToastrContext";
import styled from "styled-components";

function SomeComponent() {
  const sendToast = useContext(ToastrContext);

  return (
    <React.Fragment>
      <div>Hey! Click for some toasts!</div>
      <button onClick={() => sendToast("This is your toast!")}>Click</button>
    </React.Fragment>
  );
}

export default SomeComponent;

Upvotes: 1

Related Questions