why the change of variant is not captured in framer motion? - reactjs

I am trying to perform an animation fadein and fadeout to the images in a gallery with framer motions variants. The problem is I didn't manage to trigger the second animation.
So far, I have created a holder for the animation to be displayed till the image load, which I call DotLottieWrapper. I trigger the animation of this holder in the first render and when the loading state changes but the second looks like is not happening. My code is bellow:
const [loading,setLoading] = useState(1);
const loadedDone = () => {
const animationTime = 6000
async function wait() {
await new Promise(() => {
setTimeout(() => {
setLoading(0)
}, animationTime);
})
};
wait();
}
const StyledWrapper = wrapperStyle ?
styled(wrapperStyle)`position:relative;` :
styled(imageStyle)`
${cssImageStyle(src)}
`;
const animationVariants = {
0:{
backgroundColor:'#300080',
opacity:0,
transition:{duration:1.5}
},
1:{
opacity:1,
backgroundColor:'#702090',
transition:{duration:1.5}
}
}
return (
<StyledWrapper
as={NormalDiv}
onClick={handleClick || function () { }}
>
{(loading < 2) && (
<DotLottieWrapper
initial='0'
animate={loading===1?'1':'0'}
variants={animationVariants}
key={IDGenerator()}
>
</DotLottieWrapper>
)}
{(loading < 2) && (
<img
key={IDGenerator()}
onLoad={() => {
loadedDone();
}}
src={src}
/>
)}
</StyledWrapper>
)
Have I done something wrong?

Related

Smooth scrolling down after component with pdf is displayed in reactjs

I would need smooth scrolling down to component with generated pdf. But it looks like that first is scrolled to view and then
component is mounted. How can I set waiting to component is displayed and then scroll to view this component?
When I click to button, the pdf will start to generate. After generating this pdf, I would like scroll to view this PDFViewer.
My solution does not work. 'await' has no effect on the type of this expression. OR Object is possibly 'null'.
I use reactjs 17.0.2, react-pdf/renderer 3.0.1, typescript.
Code:
import { PDFViewer } from "#react-pdf/renderer";
export const Print: NextPage = () => {
const [pdfGenerationEnabled, setPdfGenerationEnabled] = useState<boolean>(false);
const generatePDFAndView = async () => {
setPdfGenerationEnabled(true);
await document.getElementById("generated-pdf-view")?.scrollIntoView({ behavior: "smooth" });
}
return (
<DownloadAndPrint>
<h2>Generating PDF</h2>
<p>
Some long text
Some long text
Some long text
.
.
.
</p>
<Button
className="btn-generate-pdf"
onClick={() => { generatePDFAndView() }}
>
Vygenerovať pdf súbory
</Button>
{pdfGenerationEnabled
?
<>
<div id="generated-pdf-view" className="viewer-styled">
<PDFViewer showToolbar={true} style={{ height: "45vh", width: "100%" }}>
<ComponentPrint
data={data}
/>
</PDFViewer>
</div>
</>
: ""
}
</DownloadAndPrint>
);
};
EDITED: After added delay, it's works. After 3 seconds smooth scrolled to the pdf viewer. But I don't know how many seconds it should waiting? Sometimes generated pdf has 3 pages, sometimes 300 pages. According to the data.
const delay = (n) => new Promise(r => setTimeout(r, n*1000));
const generatePDFAndView = async () => {
setPdfGenerationEnabled(true);
await delay(3.0); // waiting 3s
document.getElementById("generated-pdf-view")?.scrollIntoView({ behavior: "smooth" });
}
My solution with delay and calculation time to wait according to the data. Maybe somebody it helps.
import { PDFViewer } from "#react-pdf/renderer";
export const Print: NextPage = () => {
const [pdfGenerationEnabled, setPdfGenerationEnabled] = useState<boolean>(false);
const itemsPerPage = 5;
const calculateTimeToDisplayPdfViewer = () => {
const numberOfItems = data?.items?.length;
if (numberOfItems > 0 && itemsPerPage > 0) {
let time = (numberOfItems / itemsPerPage) / 5; // 1 page = 1/5s = 0.20s
time = parseFloat(time.toFixed(2));
console.log("Waiting " + time + "s.");
return time;
} else {
return 2.0;
}
}
const delay = (n) => new Promise( r => setTimeout(r, n*1000)); // waiting in seconds
const generatePDFAndView = async () => {
setPdfGenerationEnabled(true);
await delay(calculateTimeToDisplayPdfViewer());
document.getElementById("generated-pdf-view")?.scrollIntoView({ behavior: "smooth" });
}
return (
<DownloadAndPrint>
<h2>Generating PDF</h2>
<p>
Some long text
Some long text
Some long text
.
.
.
</p>
<Button
className="btn-generate-pdf"
onClick={() => { generatePDFAndView() }}
>
Vygenerovať pdf súbory
</Button>
{pdfGenerationEnabled
?
<>
<div id="generated-pdf-view" className="viewer-styled">
<PDFViewer showToolbar={true} style={{ height: "45vh", width: "100%" }}>
<ComponentPrint
data={data}
/>
</PDFViewer>
</div>
</>
: ""
}
</DownloadAndPrint>
);
};

How to swipe card programatticaly in react using react-tinder card

I am fetching data from the Backend and loading them in the card using react-tinder-card
Swiping works properly but unable to swipe using the buttons
I follow the documentation but still did not work
Here is the sample
Swiping gestures are working fine.
But when implement by checking documentation things did not work
Things are not working and tomorrow is my project day
import React, { useEffect, useState, useContext, useRef } from "react";
function Wink() {
const [people, setPeople] = useState([]);
const [loading, setLoading] = useState(false);
const [currentIndex, setCurrentIndex] = useState(people.length - 1)
const [lastDirection, setLastDirection] = useState()
// used for outOfFrame closure
const currentIndexRef = useRef(currentIndex)
const childRefs = useMemo(
() =>
Array(people.length)
.fill(0)
.map((i) => React.createRef()),
[]
)
const updateCurrentIndex = (val) => {
setCurrentIndex(val)
currentIndexRef.current = val
}
const canGoBack = currentIndex < people.length - 1
const canSwipe = currentIndex >= 0
// set last direction and decrease current index
const swiped = (direction, nameToDelete, index) => {
setLastDirection(direction)
updateCurrentIndex(index - 1)
}
const outOfFrame = (name, idx) => {
console.log(`${name} (${idx}) left the screen!`, currentIndexRef.current)
// handle the case in which go back is pressed before card goes outOfFrame
currentIndexRef.current >= idx && childRefs[idx].current.restoreCard()
// TODO: when quickly swipe and restore multiple times the same card,
// it happens multiple outOfFrame events are queued and the card disappear
// during latest swipes. Only the last outOfFrame event should be considered valid
}
const swipe = async (dir) => {
if (canSwipe && currentIndex < db.length) {
await childRefs[currentIndex].current.swipe(dir) // Swipe the card!
}
}
// increase current index and show card
const goBack = async () => {
if (!canGoBack) return
const newIndex = currentIndex + 1
updateCurrentIndex(newIndex)
await childRefs[newIndex].current.restoreCard()
}
useEffect(() => {
setLoading(true);
axios
.post("http://localhost:4000/api/all-profile", { email })
.then(function (response) {
setPeople(response.data);
setCurrentIndex(response.data.length);
}
}, []);
return (
<div className="DateMainDiv">
<Header />
<div className="ProfieCards">
{people.map((person) => (
<TinderCard
className="swipe"
key={person.email}
ref={childRefs[index]}
preventSwipe={swipe}
onSwipe={(dir) => swiped(dir, person.name, person.email)}
onCardLeftScreen={onCardLeftScreen}
onCardUpScreen={onCardUpScreen}
>
<div
style={{ backgroundImage: `url(${person.image})` }}
className="Winkcard"
>
<img
onLoad={handleLoad}
src={person.image}
alt="Image"
className="TinderImage"
/>
<h3>
{person.name}{" "}
<IconButton
style={{ color: "#fbab7e" }}
onClick={() => handleOpen(person.email)}
>
<PersonPinSharpIcon fontSize="large" />
{parseInt(person.dist/1000)+"KM Away"}
</IconButton>
</h3>
</div>
</TinderCard>
))}
<SwipeButtonsLeft onClick={()=>{swipe("left")}} />
<SwipeButtonsLeft onClick={()=>{goback()}} />
<SwipeButtonsLeft onClick={()=>{swipe("right")}} />
</div>
</div>
);
}
export default Wink;

React Link framer motion animation with AnimatePresence

I have a Navigation component in which the Menu Items float in separately on load and float out on click.
When I added Router and changed the items to Links, the exit animation didn't work because it loaded the new Route component right away.
I want to keep the items individual animation with Link functionality.
Here is the link:
https://codesandbox.io/s/elastic-leaf-fxsswo?file=/src/components/Navigation.js
Code:
export const Navigation = () => {
const navRef = useRef(null);
const onResize = () => {
setIsColumn(window.innerWidth <= 715);
};
const [clickOnMenu, setClick] = useState(false);
const [itemtransition, setTransition] = useState(
Array(menuItems.length).fill(0)
);
const [isColumn, setIsColumn] = useState(window.innerWidth <= 715);
const click = (e) => {
const copy = [...itemtransition];
const index = e.target.id;
setTransition(copy.map((e, i) => (Math.abs(index - i) + 1) / 10));
setTimeout(() => setClick(true), 50);
};
useEffect(() => {
window.addEventListener("resize", onResize);
return () => window.removeEventListener("resize", onResize);
}, []);
return (
<AnimatePresence exitBeforeEnter>
{!clickOnMenu && (
<Nav ref={navRef}>
{menuItems.map((e, i) => {
const text = Object.keys(e)[0];
const value = Object.values(e)[0];
return (
<Item
id={i}
key={value}
animate={{
x: 0,
y: 0,
opacity: 1,
transition: { delay: (i + 1) / 10 }
}}
initial={{
x: isColumn ? 1000 : 0,
y: isColumn ? 0 : 1000,
opacity: 0
}}
exit={{
x: isColumn ? -1000 : 0,
y: isColumn ? 0 : -1000,
opacity: 0,
transition: { delay: itemtransition[i] }
}}
onClick={click}
>
{/*<Link to={`/${value}`}>{text}</Link>*/}
{text}
</Item>
);
})}
</Nav>
)}
</AnimatePresence>
);
};
In the sandbox in Navigation.js 69-70. row:
This is the desired animation.
69. {/*<Link to={`/${value}`}>{text}</Link>*/}
70. {text}
But when I use Link there is no exit animation
69. <Link to={`/${value}`}>{text}</Link>
70. {/*text*/}
Is there a workaround or I should forget router-dom.
Thank you in forward!
This may be a bit hackish, but with routing and transitions sometimes that is the nature. I suggest rendering the Link so the semantic HTML is correct and add an onClick handler to prevent the default navigation action from occurring. This allows any transitions/animations to go through. Then update the click handler of the Item component to consume the link target and issue an imperative navigation action on a timeout to allow transitions/animations to complete.
I used a 750ms timeout but you may need to tune this value to better suit your needs.
Example:
...
import { Link, useNavigate } from "react-router-dom";
...
export const Navigation = () => {
const navRef = useRef(null);
const navigate = useNavigate(); // <-- access navigate function
...
const click = target => (e) => { // <-- consume target
const copy = [...itemtransition];
const index = e.target.id;
setTransition(copy.map((e, i) => (Math.abs(index - i) + 1) / 10));
setTimeout(() => {
setClick(true);
}, 50);
setTimeout(() => {
navigate(target); // <-- navigate after some delta
}, 750);
};
...
return (
<AnimatePresence exitBeforeEnter>
{!clickOnMenu && (
<Nav ref={navRef}>
{menuItems.map((e, i) => {
const text = Object.keys(e)[0];
const value = Object.values(e)[0];
return (
<Item
...
onClick={click(`/${value}`)} // <-- pass target to handler
>
<Link
to={`/${value}`}
onClick={e => e.preventDefault()} // <-- prevent link click
>
{text}
</Link>
</Item>
);
})}
</Nav>
)}
</AnimatePresence>
);
};
...

Re-rendering React component when I call useState hook

I have a problem with the re-rendering React component when I call useState() hook inside image.onload(). I'm expecting the component to be re-render once after I call setClassificationResult, but of some reason, it's re-rendering all the time, like I have some infinite loop. Here is my code:
const ImageClassification = React.memo(function() {
const [isModelLoaded, setModelLoaded] = useState(false);
const [uploadedFile, setUploadedFile] = useState();
const [classifier, setClassifier] = useState();
const [classificationResult, setClassificationResult] = useState();
useEffect(() => {
async function modelReady() {
setClassifier(
await MobileNet.load().then(model => {
setModelLoaded(true);
return model;
})
);
}
modelReady();
}, []);
function onDrop(acceptedFiles: File[]) {
setUploadedFile(acceptedFiles);
}
function prepareImage(inputFile: File) {
const image = new Image();
let fr = new FileReader();
fr.onload = function() {
if (fr !== null && typeof fr.result == "string") {
image.src = fr.result;
}
};
fr.readAsDataURL(inputFile);
image.onload = async function() {
const tensor: Tensor = tf.browser.fromPixels(image);
classifier.classify(tensor).then((result: any) => {
// Crazy re-rendering happens when I call this hook.
setClassificationResult(result);
console.log(result);
});
console.log("Tensor" + tensor);
};
}
const { getRootProps, getInputProps, isDragActive } = useDropzone({ onDrop });
return (
<React.Fragment>
{!isModelLoaded ? (
<CircularProgress />
) : (
<div {...getRootProps()}>
<input {...getInputProps()} />
{isDragActive ? (
<p>Drop the files here.. </p>
) : (
<p>Drag 'n' drop some files here, or click to select files</p>
)}
{uploadedFile &&
uploadedFile.map((item: File) => {
prepareImage(item);
return classificationResult
? classificationResult.map((result: any) => {
return (
<ClassificationResult
className={result.className}
probability={result.probability}
/>
);
})
: null;
})}
</div>
)}
</React.Fragment>
);
});
export default ImageClassification;
Any idea of how to avoid that crazy re-rendering?
You have a lifecycle issue with your component, as it call prepareImage(item) from your return html value. This means you will call this function at each rendering which is why it create some infinite-loop crazy re-redering.
You need to rethink your algorithm and move it to a better location. A good solution would to only prepareImage onDrop event so it is done only once.
function onDrop(acceptedFiles: File[]) {
setUploadedFile(acceptedFiles);
acceptedFiles.forEach(file => {
prepareImage(file);
});
}
Then maybe store in state an array of Image which should be dislayed and be prepared 😉.

React rewriting class with hooks causes re-render loops

I am working on an image transition react component, where it waits to have img1 loaded and then on a user click loads the img2, but fades img1 smoothly.
Tried re-writing the application with hooks to set the states - but when this is applied it creates an re-render loop error.
Is it because we are always setting img1 as currentImgSrc initially?
const [imgSrcOne, setImgSrcOne] = useState(currentImgSrc)
errors in the if/else block? or is setting the useState in the return causing the bug
tried removing the if/else block to make the application functional
https://jsfiddle.net/b531L6ho/
import {
ImageTransition
} from './imageTransition/imageTranition'
const {
useState
} = React
interface ImageContainerProps {
layer: string
currentImgSrc: string
notifyOnError: (url: string) => void
updateLayerLoading: (hasLoaded: boolean) = void
}
export const ImageTransitionContainer: React.SFC < ImageContainerProps > = ({
currentImgSrc,
layer,
notifyOnError,
updateLayerLoading
}) => {
const [imgSrcOne, setImgSrcOne] = useState(currentImgSrc)
const [displayImgOne, setdisplayImgOne] = useState(true)
const [imgOneLoaded, setImgOneLoaded] = useState(false)
const [imgSrcTwo, setImgSrcTwo] = useState(currentImgSrc)
const [displayImgTwo, setdisplayImgTwo] = useState(true)
const [imgTwoLoaded, setImgTwoLoaded] = useState(false)
if (imgSrcOne && currentImgSrc !== imgSrcOne) {
console.log("in the if statement")
setImgSrcTwo(currentImgSrc)
setDisplayImgTwo(two)
}
if (currentImgSrc !== imgSrcOne) {
setImgSrcne(currentImgSrc)
}
if (!imgSrcOne && !imgSrcTwo) {
setImgSrcOne(currentImgSrc)
setDisplayImgOne(true)
} else if (imgSrcOne && currentImgSrc !== imgSrcOne) {
setImgSrcTwo(currentImgSrc)
setDisplayImgTwo(true)
} else if (imgSrcTwo && currentImgSrc !== imgSrcTwo) {
setImgSrcOne(currentImgSrc)
setDisplayImgOne(true)
}
console.log("state --", imgSrcOne, displayImgOne, imgOneLoaded, imgSrcTwo, displayImgTwo, imgTwoLoaded)
return (
<>
<ImageTransition
displayImg={displayImgOne}
imgLoaded={imgOneLoaded}
imgSrc={imgSrcOne}
onExit={() => {
setImgSrcOne(null)
setImgOneLoaded(false)
}}
onLoad={() => {
setImgOneLoaded(true)
setDisplayImgTwo(false)
}}
/>
<ImageTransition
displayImg={displayImgTwo}
imgLoaded={imgTwoLoaded}
imgSrc={imgSrcTwo}
onExit={() => {
setImgSrcTwo(null)
setImgTwoLoaded(false)
}}
onLoad={() => {
setImgTwoLoaded(true)
setDisplayImgOne(false)
}}
/>
</>
)
}

Resources