Framer Motion 'Exit' property not working with Nextjs - next.js

I just started working with Framer motion and Nextjs, and I ran into a problem where I am unable to get the exit property to animate. The code below shows that I am trying to toggle the word 'Hello' on and off, and while the word appears when toggled on, the exit animation does not trigger when removed. The Framer API docs suggest that AnimatePresence needs to wrap the motion.div as seen below in order for the exit animation to trigger, but this has not fixed the issue.
Any help would be appreciated.
import React from "react";
import classes from "./FramerMotion.module.css";
import { motion, AnimatePresence, } from "framer-motion";
import { useState } from "react";
function FramerMotion() {
const [words, setWords] = useState(true);
const toggleWords = (event) => {
setWords(words === false);
console.log(words);
};
const codeVariant = {
start: {
opacity: 0,
},
middle: {
opacity: 1,
transition: {
duration: 3,
},
},
end: {
opacity: 0,
transition: {
duration: 3,
},
},
};
return (
<>
<div className={classes.container}>
{words && (
<AnimatePresence>
<motion.h1
key="modal"
variants={codeVariant}
initial="start"
animate="middle"
exit="end"
>
Hello
</motion.h1>
</AnimatePresence>
)}
</div>
<button onClick={toggleWords}>Show/Hide</button>
</>
);
}
export default FramerMotion;

I solved this problem. The problem was that AnimatePresence was improperly placed.
Fix:
return (
<>
<div className={classes.container}>
<AnimatePresence>
{words && (
<motion.h1
key="modal"
variants={codeVariant}
initial="start"
animate="middle"
exit="end"
>
Hello
</motion.h1>
)}
</AnimatePresence>
</div>

Related

Unable to make Nextjs 12 code work in 13 with fetching getting issues

I am trying to convert my existing code from Next js 12 to 13. But I am unable to make it work. How should I transform to make it work? Getting errors like you can't use "useState" etc.
import { useState } from 'react';
import Head from 'next/head';
import { loadData } from './api/post';
import {
Section,
Cover,
SocialNetworks,
BuyMeCoffee,
Title,
PostGrid,
Post,
Button
} from '../components';
const LOAD_MORE_STEP = 4;
export default function Home({ initialPosts, total }) {
const [ posts, setPosts ] = useState(initialPosts);
const [ loadedAmount, setLoadedAmount ] = useState(LOAD_MORE_STEP);
const [ loading, setLoading ] = useState(false);
const showLoadButton = total > loadedAmount;
const getMorePosts = async () => {
setLoading(true);
try {
const data = await fetch(`/api/post?start=${loadedAmount}&end=${loadedAmount + LOAD_MORE_STEP}`).then((response) => response.json());
setLoadedAmount(loadedAmount + LOAD_MORE_STEP);
setPosts([...posts, ...data.posts])
setLoading(false);
} catch (error) {
console.log(error);
setLoading(false);
}
};
return (
<div style={{
marginBottom: '1rem',
}}>
<Head>
<title>My blog</title>
</Head>
<Section>
<Cover title="Elena<br />Litvinova" />
<SocialNetworks />
<BuyMeCoffee />
</Section>
<Section>
<Title>New Post</Title>
<PostGrid>
{posts.map((post) => (
<Post
key={post._id}
{...post}
/>
))}
</PostGrid>
{showLoadButton && (
<div style={{
display: 'flex',
justifyContent: 'center',
}}>
<Button
disabled={loading}
onClick={getMorePosts}
>
Load more posts...
</Button>
</div>
)}
</Section>
</div>
)
}
export async function getServerSideProps() {
const { posts, total } = await loadData(0, LOAD_MORE_STEP);
return {
props: {
initialPosts: posts,
total
},
}
}
React hooks are only available on the client. You have to add 'use client' to the top of your file, if it is a client component. In Next.js 13 all components are React server component by default. Here are the docs for server and client components in Next.js
I would also recommend you to fetch your data with #tanstack/react-query.

Make react swiper pagination as a separate component and plug it in swiper

I am working on a project with a setup combination of react, tailwind and react icons. In my project multiple sliders are required (some has navigation, some has pagination etc…)
So, I created a slider factory component Carousel and controlling variation through props
// Carousel/index.jsx
import { Children, useState } from "react";
import { v4 as uuidv4 } from "uuid";
import { HiOutlineArrowLeft, HiOutlineArrowRight } from "react-icons/hi";
import { Swiper, SwiperSlide } from "swiper/react";
import { Autoplay, Keyboard, Mousewheel, Navigation, Pagination } from "swiper";
import "swiper/css";
import "swiper/css/pagination";
import "swiper/css/navigation";
import NextSlideBtn from "./NextSlideBtn";
import PrevSlideBtn from "./PrevSlideBtn";
const Carousel = ({
children,
loop,
pagination,
navigation,
autoplay,
breakpoints,
}) => {
const childrenArray = Children.toArray(children);
const [swiperRef, setSwiperRef] = useState();
return (
<div className="group relative">
<Swiper
className="rounded overflow-hidden"
onSwiper={setSwiperRef}
modules={[
Autoplay,
Keyboard,
Mousewheel,
Pagination,
Navigation,
]}
mousewheel
keyboard
grabCursor
loop={loop || false}
autoplay={
autoplay && {
delay: 5000,
disableOnInteraction: true,
}
}
spaceBetween={breakpoints && 20}
breakpoints={
breakpoints && {
450: {
slidesPerView: 2,
},
768: {
slidesPerView: 3,
},
1536: {
slidesPerView: 4,
},
}
}
pagination={
pagination && {
clickable: true,
}
}
>
{childrenArray.map((item) => (
<SwiperSlide key={uuidv4()}>{item}</SwiperSlide>
))}
</Swiper>
{navigation && (
<div className="hidden group-hover:block">
<PrevSlideBtn swiperRef={swiperRef}>
<HiOutlineArrowLeft className="text-violet-600" />
</PrevSlideBtn>
<NextSlideBtn swiperRef={swiperRef}>
<HiOutlineArrowRight className="text-violet-600" />
</NextSlideBtn>
</div>
)}
</div>
);
};
export default Carousel;
// Carousel/NextSlideBtn.jsx
const NextSlideBtn = ({ children, swiperRef }) => {
const handleNextSlide = () => swiperRef.slideNext();
return (
<button
onClick={handleNextSlide}
className="absolute z-50 top-1/2 -translate-y-1/2 -right-2 translate-x-2 bg-white shadow-md rounded-full p-3"
>
{children}
</button>
);
};
export default NextSlideBtn;
// Carousel/PrevSlideBtn.jsx
const PrevSlideBtn = ({ children, swiperRef }) => {
const handlePrevSlide = () => swiperRef.slidePrev();
return (
<button
onClick={handlePrevSlide}
className="absolute z-50 top-1/2 -translate-y-1/2 -left-2 -translate-x-2 bg-white shadow-md rounded-full p-3"
>
{children}
</button>
);
};
export default PrevSlideBtn;
One of the requirement was to create custom navigation button, which I managed to accomplish as you can see in the code above, but for more visibility I am marking the flow in short
// Carousel/index.jsx
const [swiperRef, setSwiperRef] = useState();
<div>
<Swiper
onSwiper={setSwiperRef}
...
>
...
</Swiper>
<PrevSlideBtn swiperRef={swiperRef} />
<NextSlideBtn swiperRef={swiperRef} />
</div>
// Carousel/NextSlideBtn.jsx
const NextSlideBtn = ({ children, swiperRef }) => {
const handleNextSlide = () => swiperRef.slideNext();
return (
<button
onClick={handleNextSlide}
...
>
{children}
</button>
);
};
export default NextSlideBtn;
// Carousel/PrevSlideBtn.jsx
const PrevSlideBtn = ({ children, swiperRef }) => {
const handlePrevSlide = () => swiperRef.slidePrev();
return (
<button
onClick={handlePrevSlide}
...
>
{children}
</button>
);
};
export default PrevSlideBtn;
Couple of github issues, stackoverflow questions and swiper docs helped me out to accomplish this, and it was pretty straight forward to understand.
Now my question is how can I customize swiper react pagination (like radio buttons which some websites use, or any other styles…) using tailwind and I want to make it as a separate component like I did for navigation buttons.
I’ve tried many solutions eg:
const paginationBullets = {
type: "custom",
clickable: true,
renderCustom: (_, current, total) => {
return <MyCustomSwiperPaginationComponent current={current} total={total} />;
},
};
But nothing seems to work as expected.
Please help me out.
Component separation is my goal.

React transition inline style won't apply

I am working with custom Transition component and my inline styling just wont apply. I tried everything what is in my power and just got myself angry. Can somebody explain why this doesn't work.
Modal show itself on initial load and by clicking on button for show or not show this doesn't react. I mean state updates correctly (this console.log shows 0 and 1 when it should) but styling just wont apply.
import classes from "./App.module.css";
import ReactDOM from "react-dom";
import React from "react";
import Modal from "./components/Modal/Modal";
import List from "./components/List/List";
import Backdrop from "./components/Backdrop/Backdrop";
import { useState } from "react";
import Transition from "react-transition-group/cjs/Transition";
function App() {
const [modalOpen, setModalOpen] = useState(false);
const onOpenModalHandler = () => {
setModalOpen(true);
};
const onCloseModalHandler = () => {
setModalOpen(false);
};
return (
<div className={classes["App"]}>
<h1>React Animations</h1>
<Transition in={modalOpen} timeout={1000} onMountEnter onMountExit>
{(state) => {
console.log(state);
console.log(state === "exiting" || state === "exited" ? 0 : 1);
return ReactDOM.createPortal(
<Modal
closeModal={onCloseModalHandler}
modalState={modalOpen}
style={{
transition: `opacity 300ms ease-out`,
opacity: state === "exiting" || state === "exited" ? 0 : 1,
}}
/>,
document.getElementById("modal")
);
}}
</Transition>
{modalOpen &&
ReactDOM.createPortal(
<Backdrop />,
document.getElementById("backdrop")
)}
<button className={"Button"} onClick={onOpenModalHandler}>
Open Modal
</button>
<h3>Animating Lists</h3>
<List />
</div>
);
}
export default App;
Modal
import classes from "./Modal.module.css";
const Modal = (props) => {
const classNames = props.modalState
? `${classes["modal-open"]} ${classes["Modal"]}`
: `${classes["modal-close"]} ${classes["Modal"]}`;
const onCloseModalHandler = () => {
props.closeModal();
};
return (
<div className={classNames}>
<h1>A Modal</h1>
<button className="Button" onClick={onCloseModalHandler}>
Dismiss
</button>
</div>
);
};
export default Modal;

Can't scroll down after page changed using Locomotive scroll and react-transition-group

I have installed Locomotive scroll and React Transition Group. but when I click on the link or open a page I can't scroll anymore and it will stick at the top of the screen.
I have added scroll.destory() in my useEffect but it doesn't work.
What should I do?
import { useEffect } from 'react';
import Head from 'next/head';
import { SwitchTransition, Transition } from 'react-transition-group';
import gsap from 'gsap';
import '#/styles/styles.scss';
export default function MyApp({ Component, pageProps, router }) {
function enter(node) {
gsap.from(node, {
duration: 0.5,
autoAlpha: 0,
});
}
function exit(node) {
gsap.to(node, {
duration: 0.5,
autoAlpha: 0,
});
}
useEffect(() => {
let scroll;
import('locomotive-scroll').then(
(locomotiveModule) => {
scroll = new locomotiveModule.default({
el: document.querySelector('[data-scroll-container]'),
smooth: true,
smoothMobile: false,
resetNativeScroll: true,
});
},
);
// `useEffect`'s cleanup phase
return () => scroll.destroy();
}, []);
return (
<>
<SwitchTransition>
<Transition
key={router.pathname}
timeout={100}
in={true}
onEnter={enter}
onExit={exit}
>
<main data-scroll-container className="container">
<Component {...pageProps} />
</main>
</Transition>
</SwitchTransition>
</>
);
}

AnimeJs implementation with react doesn't work

I've implemented a simple Anime Js text animation into my react app; I have other other Anime Js projects which perform perfectly but I just don't see the issue with this one, and why it doesn't work.
TextColourChangeLoader.js
import React from 'react';
import './styles/TextColourChangeLoader.css';
import anime from 'animejs';
export default class TextColourChangeLoader extends React.Component {
constructor(props) {
super(props);
this.state = {
text: "",
play: false
}
}
componentDidMount() {
this.setState({
text: this.props.textToRender,
play: true
})
}
componentWillUnmount() {
this.setState({
text: '',
play: false
})
}
playAnimeColourChange = () => {
if(this.state.play) {
anime({
targets: '.loader-letter',
delay: anime.stagger(100),
duration: 1000,
scale: anime.stagger(2, {easing: "easeInElastic"}),
color: '#7DE2FC',
direction: 'alternate',
easing: 'easeInOutElastic(1,.8)',
loop: true,
loopComplete: function() {
console.log('done')
}
});
}
}
render() {
this.playAnimeColourChange();
return (
<div id="loader-text-holder">
{this.state.text.split('').map((letter, i) => {
return <span id={letter === " " ? "loader-letter-space" : "loader-letter"+i} key={i} className="loader-letter text">{letter}</span>
})}
{this.playAnimeColourChange()}
</div>
)
}
}
Example.js
import React from 'react';
import './styles/ViewLoaderWithText.css';
import anime from 'animejs';
import TextColourChangeLoader from './TextColourChangeLoader';
export default class ViewLoaderWithText extends React.Component {
constructor(props) {
super(props);
this.state = {
play: false
}
}
componentDidMount() {
this.setState({
play: true
})
}
playAnime = () => {
if(this.state.play) {
let loaderAnime = anime({
targets: '.view-loader-shape',
delay: anime.stagger(100, {start: -100}),
translateY: [{value: -25, easing: 'easeInCubic'}, {value: 0, easing: 'easeOutCirc'}],
background: '#7DE2FC',
direction: 'alternate',
easing: 'easeInOutElastic(1,.8)',
duration: 1000,
loop: true,
autoplay: true
});
}
}
render() {
return (
<div id="view-loader-wrapper">
<div id="loader-shape-holder">
<span className="view-loader-shape" id="view-loader-shape1"></span>
<span className="view-loader-shape" id="view-loader-shape2"></span>
<span className="view-loader-shape" id="view-loader-shape3"></span>
<span className="view-loader-shape" id="view-loader-shape4"></span>
<span className="view-loader-shape" id="view-loader-shape5"></span>
</div>
<TextColourChangeLoader textToRender="Verifying email"/>
{this.playAnime()}
</div>
)
}
}
The anime js instance in the Example.js file works as it should; however the TextColourChangeLoader.js animation doesn't run. I've tried logging to the console each time a loop completes with the loopComplete callback option, and it shows that the loop is running however, the animation doesn't. I've also tried only running the TextColourChangeLoader.js animation, but that still doesn't work. What could be a possible explanation for this? Anything helps.
I think that doesn't work because you are trying to animate a component that had not loaded first. Remember the lifecycle flow in React, I prefer use React Hooks to solve the problem and this is the way: with the useEffect hook you pass in the animate function, in order to first render the component and then execute the function, not in the reverse order. I guess that the equivalent in class components is componentDidMount() so try to refactorize your code in order to connect the anime() with the componentDidMount. Conclusion, the main concept is first render the component and then execute the anime(). Sorry for my english level.

Resources