Reputation: 70
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
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
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