React Framer-motion AnimatePresence exit animation does not work - javascript

I am trying to animate some page transitions, but for some reason only the entering animation works.
The text slides in properly, but it does not slide out when routing to another component, it just disappears instead. I have tried assigning the keys to individual components using location.pathname and also to the Outlet component, adding exitBeforeEnter in AnimatePresence and mode="wait", so i have no idea what else could be wrong.
The main component:
import React from "react";
import Navbar from './navbar.js';
import { Outlet } from "react-router-dom"
import { AnimatePresence } from "framer-motion"
export default function App() {
return (
<div>
<Navbar/>
<AnimatePresence>
<Outlet/>
</AnimatePresence>
</div>
);
}
The pages look like this:
import React from 'react';
import './index.css'
import { motion } from "framer-motion"
import { useLocation } from 'react-router-dom';
export default function Section1() {
let location = useLocation()
return (
<motion.p
key={location.pathname}
transition={{duration: 0.8, ease: "easeOut"}}
initial={{ x: "-100%", opacity: 0 }}
animate={{ x: 0, opacity: 1 }}
exit={{ x: "100%", opacity: 0 }} className='text-center text-6xl my-56'>Section 1</motion.p>
)
}
Navbar:
import React from "react"
import { Link } from "react-router-dom";
export default function Navbar(router) {
return (
<nav className='bg-gray-300 flex items-center justify-between'>
<p className="ml-6 text-3xl">Варвара Алексеева</p>
<ul className='flex flex-row justify-end mr-20 text-xl'>
<li className='m-4'><Link to={"/"} className='hover:bg-lapis hover:text-white p-2 rounded-xl duration-300'>Main</Link></li>
<li className='m-4'><Link to={"/section1"} className='hover:bg-lapis hover:text-white p-2 rounded-xl duration-300'>Section 1</Link></li>
<li className='m-4'><Link to={"/section2"} className='hover:bg-lapis hover:text-white p-2 rounded-xl duration-300'>Section-2 2</Link></li>
<li className='m-4'><Link to={"/section3"} className='hover:bg-lapis hover:text-white p-2 rounded-xl duration-300'>Section 3</Link></li>
</ul>
</nav>
)
}

While there does not seem to be official guides about this, I think the reason could be Outlet wraps the elements for nested routes with another component (Provider), causing AnimatePresence unable to work, since these elements are no longer direct children of it.
As an experimental solution, the exit animation seems to work by using useOutlet to manually render the nested routes, and pass a unique key with cloneElement to the element for the route.
Live demo of the experiment: stackblitz (omitted styles for simplicity)
import { useLocation, useOutlet } from 'react-router-dom';
import { AnimatePresence } from "framer-motion"
export default function App() {
const { pathname } = useLocation();
const element = useOutlet();
return (
<div className="App">
<Navbar />
<AnimatePresence mode="wait">
{element && React.cloneElement(element, { key: pathname })}
</AnimatePresence>
</div>
);
}

Related

Rendering Issue in React

Ive been going insane for the past hour trying to figure out his bug "Warning: Cannot update a component (App) while rendering a different component (Nav). To locate the bad setState() call inside Nav, follow the stack trace as described in https://reactjs.org/link/setstate-in-render"
import React, {useState, useEffect} from 'react'
import React, {useState, useEffect} from 'react'
import './App.css';
import Cart from './pages/cart'
import Home from './pages/home'
import Drawings from './pages/drawings'
import Paintings from './pages/paintings'
import Photos from './pages/photos'
import Nav from './pages/components/nav'
const App = ()=> {
const [page, setPage] = useState('home');
if(page=='home')
return(
<>
<Nav setPage={setPage}/>
<Home/>
</>
)
else if(page=='drawings')
return(
<>
<Nav setPage={setPage}/>
<Drawings/>
</>
)
}
export default App;
Nav.js
import React from 'react'
import 'C:/Users/Bharat/Desktop/ecommerce/vgogh/src/App.css'
const Nav = ({setPage}) => {
return (
<>
<nav className='navBar'>
<div className="home"><button onClick={setPage('home')}>Home</button></div>
<ul>
<li className="navItems"><button onClick={()=>setPage('paintings')}>Painting</button></li>
<li className="navItems"><button onClick={()=>setPage('photos')}>Photos</button></li>
<li className="navItems"><button onClick={()=>setPage('drawings')}>Drawings</button></li>
<li className="navItems" id='cart'><button onClick={()=>setPage('cart')}>Cart</button></li>
</ul>
</nav>
</>
)
}
export default Nav
In Nav.js
Instead of writing this:
<button onClick={setPage('home')}>Home</button>
You can write :
<button onClick={()=>setPage('home')}>Home</button>
I think you got it.
To know more about handling event you can read their documentation. https://reactjs.org/docs/handling-events.html
After some scouring, I figured out I missed an arrow function in my "home" button in nav.js which as plichard pointed out caused an infinite loop.
Previous:
<div className="home">
<button onClick={setPage('home')}>Home</button>
</div>`
Solved:
<div className="home">
<button onClick={()=>setPage('home')}>Home</button>
</div>`

How to pass child state data to parent component

I want to pass a string from a child component to its parent, which in turn will update the data passed to the child component. For example, when a link is clicked, change planet data to reflect new planet.
There are three components.
Parent
Middle-man
Child
Hierarchy
Home => Navbar => ExpandedMenu
Parent Component
I have mapped each planet to an object called planetData from the json data. When a link is clicked, I want it to update the contents assigned to the planet passed.
import React, { useState, useEffect } from "react";
import Navbar from "../../shared/components/Navbar/Navbar";
import MobileView from "../../home/components/MobileView/MobileView";
import TabletView from "../../home/components/TabletView/TabletView";
import DesktopView from "../../home/components/DesktopView/DesktopView";
import Page from "../../shared/interface/Page/Page";
import data from "../../db/data.json";
function Home() {
// responds to events, changes active planet to represent current
const [activePlanet, setActivePlanet] = useState(data[0]);
// map each planet in json data to objects
const planetData = {
mercury: data[0],
venus: data[1],
earth: data[2],
mars: data[3],
jupiter: data[4],
saturn: data[5],
uranus: data[6],
neptune: data[7],
};
// render complete page to document
return (
<Page>
<Navbar />
{/* Conditional rendering based on screen width */}
{isDesktop && <DesktopView content={activePlanet} />}
{isTablet && <TabletView content={activePlanet} />}
{isMobile && <MobileView content={activePlanet} />}
</Page>
);
}
export default Home;
Middle Component
Component is purely a middle man.
import React, { useState } from "react";
import HamburgerMenu from "../../../assets/icon-hamburger.svg";
import ExpandedMenu from "../ExpandedMenu/ExpandedMenu";
import NavMenu from "../NavMenu/NavMenu";
function Navbar() {
const [isVisible, setIsVisible] = useState(true);
return (
<nav>
<NavMenu />
<ExpandedMenu isToggled={isVisible} />
</nav>
);
}
export default Navbar;
Child Component
When a link is clicked, it updates the state to a string representing the planet.
import React, { useState } from "react";
import NavLink from "../NavLink/NavLink";
import Arrow from "../../../assets/icon-chevron.svg";
function ExpandedMenu({ isToggled }) {
const [planet, setPlanet] = useState("Mercury");
return (
<div>
{/* Mercury */}
<div>
<div>
<NavLink
clickAction={() => {
setPlanet("Mercury");
}}
>
Mercury
</NavLink>
</div>
<img src={Arrow} alt="arrow" />
</div>
{/* Venus */}
<div>
<div>
<NavLink
clickAction={() => {
setPlanet("Venus");
}}
>
Venus
</NavLink>
</div>
<img src={Arrow} alt="arrow" />
</div>
</div>
);
}
export default ExpandedMenu;

Color not rendering from props

So, I'm passing in props to my JSX component, and then setting that props into a gradient from black to that prop. But whenever I try this, the gradient ends up going from black to just a transparent background.
import React from 'react'
import Color from './color'
const App = () => {
return (
<div className="h-screen w-screen">
<Color color="red-400" />
</div>
)
}
export default App
import React from 'react'
const color = props => {
return (
<div className="h-screen w-screen">
<div className={`h-full w-full absolute bg-gradient-to-r from-cyan-500 to-${props.color}`}>
{props.text}
</div>
</div>
)
}
export default color
What should I do?
Tailwind statically scans your files. It cannot interpolate strings. So whenever you pass a class, you have the pass the whole thing. 'to-red-500' instead of `to-${'red-500'}`
Following changes should make it work(should probably update the prop name from color to say tocolor):
import React from 'react'
import Color from './color'
const App = () => {
return (
<div className="h-screen w-screen">
<Color color="to-red-400" />
</div>
)
}
export default App
import React from 'react'
const color = props => {
return (
<div className="h-screen w-screen">
<div className={`h-full w-full absolute bg-gradient-to-r from-cyan-500 ${props.color}`}>
{props.text}
</div>
</div>
)
}
export default color

How do I make my ReactJs app display A different message when In different screens?

I have to clone a website using ReactJs which only works on desktop. When it is viewed in a mobile view or Tablet...it shows "SITE NOT AVAILABLE ON MOBILE". I want to do that too....but it is not working on my site
import "./App.css";
import Navbar from "./components/Navbar";
import Text from "./components/Text";
import Slider from "./components/Slider";
import Wallet from "./components/Wallet";
import Dropdown from "./components/Dropdown";
import MobileTablet from "./components/MobileTablet";
import { BrowserView, MobileView } from "react-device-detect";
import { BrowserRouter as Router } from "react-router-dom";
function App() {
return (
<>
<BrowserView>
<Router>
<Text />
<div className="box">
<Navbar />
<Dropdown />
<div className="box2">
<Slider />
</div>
</div>
<div className="box3">
<Wallet />
</div>
</Router>
</BrowserView>
<MobileView>
<MobileTablet />
</MobileView>
</>
);
}
export default App;
This is the code for App.js the main part....Can someone help me make my app responsive...since i am very new to this.
If you need any other codes pls let me know
you can use isMobile for conditional rendering
import {isMobile} from 'react-device-detect';
...
if (isMobile) {
return <MobileTablet />
}
``
Look, you can use a State to monitor the viewport of client window, then a useEffect to change it. The property window.innerWidth gives you the width of the client, and then you can specify it to work only under specific conditions:
import { useState, useEffect } from "react";
export default function App() {
const [userIsDesktop, setUserIsDesktop] = useState(true);
useEffect(() => {
window.innerWidth > 1280 ? setUserIsDesktop(true) : setUserIsDesktop(false);
}, [userIsDesktop]);
return (
<div className="App">
{userIsDesktop ? <h1>i'm a desktop</h1> : <h1>i'm a mobile</h1>}
</div>
);
}

why isnt my framer motion animation not triggering on unmount?

I'm trying to use the framer motion library to add an animation on component unmount but it's not working. I'm using the exit and initial prop values but there is no animation visible.
My app.tsx file:
import React, { useEffect, useState } from 'react';
import logo from './logo.svg';
import './App.css';
import Loader from './Components/Loader';
import { AnimatePresence, motion } from 'framer-motion';
const App: React.FC = () => {
const [loading, setLoading] = useState<boolean>(true);
useEffect(() => {
setTimeout(() => setLoading(false), 5000);
});
return (
<div className="App">
<AnimatePresence exitBeforeEnter>
{
loading ?
<Loader/>
:
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
</header>
}
</AnimatePresence>
</div>
);
};
export default App;
my loader component that I want to add the unmount animation to:
import { motion } from 'framer-motion';
import React from 'react';
import './style.css';
const Loader: React.FC = () => {
return (
<motion.div
initial={{ opacity: 1 }}
exit={{ opacity: 0 }}
>
<div className={'loader'}>
<h1>Loading...</h1>
<p>Some last minute bug fixes..</p>
</div>
</motion.div>
);
};
export default Loader;
From the docs:
Child motion components must each have a unique key prop so
AnimatePresence can track their presence in the tree.
Any string will work as long as it's unique:
<AnimatePresence exitBeforeEnter>
{
loading ?
<Loader key="loader"/>
:
<header className="App-header" key="header">
<img src={logo} className="App-logo" alt="logo" />
</header>
}
</AnimatePresence>

Categories

Resources