I’m following this part of this tutorial
(Edit: created a github project showing the issue here, diff behaviors if running dev or prod modes)
(Edit 2: sandbox project showing the differences when running "npm run dev" vs "npm run build + npm start" here)
I managed to implement the desired functionality of the scrolling line and the closing circles.
But when deployed to production, the circles seem to be using all the same ref, as they’re all synchronized instead of completing independently as the user scrolls down.
Added an imagine placing the behavior side by side to better illustrate whats happening. Again, this only happens when running a production build of the project
Here’s the relevant code of the circles
ExperienceDetail.tsx
import React, { useRef } from 'react';
import { Job } from './jobList';
import styles from './ExperienceDetail.module.css';
import ListIcon from './ListIcon';
import { motion } from 'framer-motion';
interface DetailsProps {
job: Job;
}
const ExperienceDetail: React.FC<DetailsProps> = ({ job }) => {
const ref = React.createRef<HTMLElement>();
return (
<li
ref={ref as any}
className={`${styles.detailContainer}`}
id={job.position}
>
<ListIcon reference={ref} />
<motion.div
initial={{ y: 50 }}
whileInView={{ y: 0 }}
transition={{ duration: 0.618, type: 'spring' }}
>
<h3 className={`${styles.title} is-capitalized has-text-weight-bold `}>
{job.position}
<a
href={job.companyLink}
target="_blank"
className="has-text-primary-dark is-capitalized "
>
@{job.company}
</a>
</h3>
<span className="is-capitalized has-text-weight-medium has-text-grey">
{job.time} | {job.location}
</span>
<p
className={'mt-2 has-text-weight-medium'}
dangerouslySetInnerHTML={{ __html: job.description }}
/>
</motion.div>
</li>
);
};
export default ExperienceDetail;
ListIcon.tsx
import React from 'react';
import styles from './ListIcon.module.css';
import { motion, useScroll } from 'framer-motion';
interface ListIconProps {
reference: React.MutableRefObject<HTMLElement | null>;
}
const ListIcon: React.FC<ListIconProps> = ({ reference }) => {
const { scrollYProgress } = useScroll({
target: reference,
offset: ['center end', 'center center'],
});
return (
<figure className={styles.listIcon}>
<svg width="75" height="75" viewBox="0 0 100 100">
<circle cx="75" cy="50" r="20" className={styles.circle1} />
<motion.circle
cx="75"
cy="50"
r="20"
className={styles.circle2}
style={{ pathLength: scrollYProgress }}
/>
<circle
cx="75"
cy="50"
r="10"
className={`${styles.circle3} ${styles.pulse}`}
/>
</svg>
</figure>
);
};
export default ListIcon;
The ExperienceDetail component is being rendered by a map function on a parent component like this
<ul>
{list.map((item, index) => (
<ExperienceDetail item={item} key={`${type}-${index}`} />
))}
</ul>
I’ve tried undoing the map and rendering them directly, but no success.
Also tried swapping the useRef for React.createRef but again, nothing.
What could be causing this difference in behavior between local and production builds?
I’ve tried undoing the map and rendering them directly, but no success. Also tried swapping the useRef for React.createRef but again, nothing.
2
Answers
Solved the issue by passing scrollYProgress to the LiIcon component instead of passing the ref
More info: https://github.com/vercel/next.js/discussions/60745
I suspect that because the
ref
variable is passed as aprop
, React is not re-rendering the<ListIcon>
Element.On first render, the
ref
is instantiated, itscurrent
value isnull
.useScroll
falls back to track thedocumentElement
when thetarget
is null.Later, the
ref.current
is updated with the<li>
element, but React has no reason to re-render<ListIcon>
because the only propreference={ref}
hasn’t changed.Remember that
ref
s are stable references, and React only does a shallow comparison of props. So,<ListIcon>
doesn’t rerender whenref.current
is set.The
useScroll
hook inframer-motion
adds thetarget
prop as theuseEffect
dependencies, but again,ref
s are stable references. So theuseEffect
has no reason to update.https://github.com/framer/motion/blob/main/packages/framer-motion/src/value/use-scroll.ts