Whenever I try and update state in my InView component, it brings back my AnimationText to the initial state it was in until I refresh. This is only when I update my darkNavbar state to inView
. I'm wondering if this has to do with a rerender screwing up framer motion maybe?
app/page.tsx:
'use client';
import { useCallback, useEffect, useState } from 'react';
import styles from './page.module.scss';
import global from '@/scss/global.module.scss';
import { Container, Navbar, AnimatedText, Globe } from '@/components';
import { useMotionTimeline } from '@/hooks';
import { motion } from 'framer-motion';
import { timeline } from './timeline';
import cx from 'classnames';
import { InView } from 'react-intersection-observer';
const Home = () => {
const heroScope = useMotionTimeline(timeline);
const [darkNavbar, setDarkNavbar] = useState(false);
return (
<>
<motion.div ref={heroScope} className={styles.heroContainer}>
<Navbar />
<Container>
<section className={styles.heroSection}>
<div className={styles.globeContainer}>
<motion.div className={styles.globeBox} id="globe-box" initial={{ height: 0 }}>
<AnimatedText
className={cx(global.heading, styles.heroTitle)}
id="hero-text"
auto={false}
>
Optimizing your brand to maximize your impact
</AnimatedText>
</motion.div>
<Globe id="globe" />
</div>
</section>
</Container>
</motion.div>
<div className={styles.afterHero}>
<Container className={styles.oneLineContainer}>
<AnimatedText as="h2" className={cx(global.subheading, styles.oneLine)}>
We’re giving the renewable energy space a makeover.
</AnimatedText>
</Container>
<InView as="div" className={styles.expertiseContainer} onChange={(inView) => setDarkNavbar(inView)}></InView>
</div>
</>
);
};
export default Home;
components/AnimatedText.tsx:
'use client';
import React, { useEffect } from 'react';
import { motion, useAnimate, stagger } from 'framer-motion';
import { useInView } from 'react-intersection-observer';
import styles from './AnimatedText.module.scss';
type TextElements =
| 'p'
| 'span'
| 'a'
| 'strong'
| 'em'
| 'blockquote'
| 'h1'
| 'h2'
| 'h3'
| 'h4'
| 'h5'
| 'h6';
interface ComposeTagProps {
children: React.ReactNode;
}
export interface AnimatedTextProps {
as?: TextElements;
className?: string;
children: string;
auto?: boolean;
id?: string;
}
export const AnimatedText = ({
as = 'h1',
className,
children,
auto = true,
id,
}: AnimatedTextProps) => {
const textArr = children.split(' ');
const ComposeTag = ({ children }: ComposeTagProps) => {
return React.createElement(as, { className, id }, children);
};
const ComposeMotion = motion(ComposeTag);
const { ref, inView } = useInView({ triggerOnce: true });
const [scope, animate] = useAnimate();
useEffect(() => {
if (auto && inView) {
animate(
'span',
{
opacity: 1,
y: 0,
},
{
delay: stagger(0.0185),
ease: 'linear',
},
);
}
}, [inView, auto, animate]);
return (
<div ref={scope}>
<div ref={ref}>
<ComposeMotion>
{textArr.map((word, idx) => (
<motion.span
key={`${word}-${idx}`}
className={styles.word}
initial={{ opacity: 0, y: 8 }}
>
{idx === textArr.length - 1 ? word : `${word} `}
</motion.span>
))}
</ComposeMotion>
</div>
</div>
);
};
export default AnimatedText;
Ok, I was able to fix this by applying useCallback and useMemo as follows:
const ComposeTag = useCallback(
({ children }: ComposeTagProps) => {
return React.createElement(as, { className, id }, children);
},
[as, className, id],
);
const ComposeMotion = useMemo(() => motion(ComposeTag), [ComposeTag]);