How can I make this component only animate when children change

How can I make sure InfoChipContainer only animates when the chips change. I'm out of things I can memoize:
interface InfoChipProps {
style?: CSSProperties;
icon?: ReactNode;
text: string;
}

const InfoChip: React.FC<InfoChipProps> = memo((props) => {
const { style, icon, text } = props;

return (
<div className="flex gap-0.5 border-0 border-solid py-0.5 px-1" style={style}>
{icon}
<div className="whitespace-nowrap leading-tight">{text}</div>
</div>
);
});
InfoChip.displayName = "InfoChip";

interface InfoChipContainerProps {
className?: string;
style?: CSSProperties;
heightPx: number;
chips: string[];
}

const InfoChipContainer: React.FC<InfoChipContainerProps> = memo((props) => {
const { className, style, heightPx, chips } = props;
const first = useRef(true);
const memoizedChips = useMemo(() => [...chips], [...chips]);
const [items, setItems] = useState(chips);

const [trail, api] = useTrail(
items.length,
() => ({
config: { mass: 5, tension: 2000, friction: 200 },
y: heightPx,
height: heightPx,
from: { y: heightPx, height: 0 },
}),
[heightPx]
);

useEffect(() => {
console.log("here");
async function playAnimation() {
await new Promise((r) => setTimeout(r, 100));
api.start({ immediate: false, y: -heightPx });
await new Promise((r) => setTimeout(r, 500));
api.start({ immediate: true, y: heightPx });
setItems(memoizedChips);
api.start({ immediate: false, y: 0 });
}

if (first.current) {
first.current = false;
} else {
playAnimation();
}
}, [memoizedChips, heightPx, api]);

return (
<div className={clsx(className, "flex gap-1.5 overflow-hidden")} style={style}>
{trail.map((styles, i) => (
<animated.div key={i} style={styles}>
<InfoChip text={items[i]} />
</animated.div>
))}
</div>
);
});
InfoChipContainer.displayName = "InfoChipContainer";
interface InfoChipProps {
style?: CSSProperties;
icon?: ReactNode;
text: string;
}

const InfoChip: React.FC<InfoChipProps> = memo((props) => {
const { style, icon, text } = props;

return (
<div className="flex gap-0.5 border-0 border-solid py-0.5 px-1" style={style}>
{icon}
<div className="whitespace-nowrap leading-tight">{text}</div>
</div>
);
});
InfoChip.displayName = "InfoChip";

interface InfoChipContainerProps {
className?: string;
style?: CSSProperties;
heightPx: number;
chips: string[];
}

const InfoChipContainer: React.FC<InfoChipContainerProps> = memo((props) => {
const { className, style, heightPx, chips } = props;
const first = useRef(true);
const memoizedChips = useMemo(() => [...chips], [...chips]);
const [items, setItems] = useState(chips);

const [trail, api] = useTrail(
items.length,
() => ({
config: { mass: 5, tension: 2000, friction: 200 },
y: heightPx,
height: heightPx,
from: { y: heightPx, height: 0 },
}),
[heightPx]
);

useEffect(() => {
console.log("here");
async function playAnimation() {
await new Promise((r) => setTimeout(r, 100));
api.start({ immediate: false, y: -heightPx });
await new Promise((r) => setTimeout(r, 500));
api.start({ immediate: true, y: heightPx });
setItems(memoizedChips);
api.start({ immediate: false, y: 0 });
}

if (first.current) {
first.current = false;
} else {
playAnimation();
}
}, [memoizedChips, heightPx, api]);

return (
<div className={clsx(className, "flex gap-1.5 overflow-hidden")} style={style}>
{trail.map((styles, i) => (
<animated.div key={i} style={styles}>
<InfoChip text={items[i]} />
</animated.div>
))}
</div>
);
});
InfoChipContainer.displayName = "InfoChipContainer";
78 Replies
Tom
Tom•2y ago
I ran out of characters in the main post, but no matter what I memoize, InfoChipContainer always thinks that its children have changed. The react profiler shows that <animated.div> has a new style. More generally, how do people normally control animations in react? Things seem to rerender all the time and for the most part people are told 'not to worry about it unless is a performance concern'. but in cases where i want animations to play at specific times I don't understand how I'm supposed to control anything api and heightPx dont change so im only talking about memoizedChips (in this attempt ^)
Unknown User
Unknown User•2y ago
Message Not Public
Sign In & Join Server To View
Tom
Tom•2y ago
tsc complains if i do that
Unknown User
Unknown User•2y ago
Message Not Public
Sign In & Join Server To View
Tom
Tom•2y ago
Unknown User
Unknown User•2y ago
Message Not Public
Sign In & Join Server To View
Tom
Tom•2y ago
yeah
Unknown User
Unknown User•2y ago
Message Not Public
Sign In & Join Server To View
Tom
Tom•2y ago
im trying to use memoization to prevent children from changing so that the deps array stays the same so that it doesnt play the animation nextjs
Unknown User
Unknown User•2y ago
Message Not Public
Sign In & Join Server To View
Tom
Tom•2y ago
i would think so too, but (1) they dont seem to change (the profiler doesnt say they changed anyway) and (2) then im confused why its a lint rule its 100% not react spring. it changes whenever i touch something else on the page it doesnt loop, it doesnt play randomly. and when i look at the profiler. its saying children changed (for a while it was also saying that style changed
Unknown User
Unknown User•2y ago
Message Not Public
Sign In & Join Server To View
Tom
Tom•2y ago
because if you dont then its a new array reference every time
Unknown User
Unknown User•2y ago
Message Not Public
Sign In & Join Server To View
Tom
Tom•2y ago
i want the dependency to be based on the strings inside the array, not the array itself
Unknown User
Unknown User•2y ago
Message Not Public
Sign In & Join Server To View
Tom
Tom•2y ago
no, it doesnt, but i can see why thats confusing 1 sec
Unknown User
Unknown User•2y ago
Message Not Public
Sign In & Join Server To View
Tom
Tom•2y ago
so the confusing thing is that [...x] does create a new array but in this case the outer [] is the deps array ITSELF when react wants to figure out whether to rerun the effect or the memo it looks at all of the things in that array and compares them to the previous values to see if anything has changed so in this case the things in the array end up being the strings which is what i want if i dont memoize it i get a different problem which is that the <InfoChipContainer> element itself lives inside a .map() in the return value
Unknown User
Unknown User•2y ago
Message Not Public
Sign In & Join Server To View
Tom
Tom•2y ago
so that will ALWAYS make a new array for the children every time that component function runs
Unknown User
Unknown User•2y ago
Message Not Public
Sign In & Join Server To View
Tom
Tom•2y ago
sorry. work call
Unknown User
Unknown User•2y ago
Message Not Public
Sign In & Join Server To View
Tom
Tom•2y ago
the useState() is because i need to hold the old value while i play the animation
Unknown User
Unknown User•2y ago
Message Not Public
Sign In & Join Server To View
Tom
Tom•2y ago
yeah same behavior
Unknown User
Unknown User•2y ago
Message Not Public
Sign In & Join Server To View
Tom
Tom•2y ago
?
Unknown User
Unknown User•2y ago
Message Not Public
Sign In & Join Server To View
Tom
Tom•2y ago
when i change the strings the div still needs to show the old value until halfway thru the animation so i need to have them somewhere and i know the code looks entangled thats why i was complaining 😛 it didnt start out like this. this is just my best atempt
Unknown User
Unknown User•2y ago
Message Not Public
Sign In & Join Server To View
Tom
Tom•2y ago
the animation works by sliding everything up and then once everything is hidden it transitions to the new strings and plays the slide in animation i mean ideally it would be even more complicated because theoretically i should be able to show the bottom of the old value and the top of the new value at the same time which would require 2 divs this IS the dumbed down version
Unknown User
Unknown User•2y ago
Message Not Public
Sign In & Join Server To View
Tom
Tom•2y ago
have you done somethingm like this with framer motion?
Unknown User
Unknown User•2y ago
Message Not Public
Sign In & Join Server To View
Tom
Tom•2y ago
ive never used it before just by changing the prop that was the idea props.chips
Unknown User
Unknown User•2y ago
Message Not Public
Sign In & Join Server To View
Tom
Tom•2y ago
yes
Unknown User
Unknown User•2y ago
Message Not Public
Sign In & Join Server To View
Tom
Tom•2y ago
right, and i do that in the useEffect()
Unknown User
Unknown User•2y ago
Message Not Public
Sign In & Join Server To View
Tom
Tom•2y ago
i just want it to start out with the initial value
Unknown User
Unknown User•2y ago
Message Not Public
Sign In & Join Server To View
Tom
Tom•2y ago
items should only update halfway thru the animation
Unknown User
Unknown User•2y ago
Message Not Public
Sign In & Join Server To View
Tom
Tom•2y ago
i do that here:
async function playAnimation() {
await new Promise((r) => setTimeout(r, 100));
api.start({ immediate: false, y: -heightPx });
await new Promise((r) => setTimeout(r, 500));
api.start({ immediate: true, y: heightPx });
setItems(React.Children.toArray(children)); // -----> here
api.start({ immediate: false, y: 0 });
}
async function playAnimation() {
await new Promise((r) => setTimeout(r, 100));
api.start({ immediate: false, y: -heightPx });
await new Promise((r) => setTimeout(r, 500));
api.start({ immediate: true, y: heightPx });
setItems(React.Children.toArray(children)); // -----> here
api.start({ immediate: false, y: 0 });
}
thats when the contents of the div needs to change
Unknown User
Unknown User•2y ago
Message Not Public
Sign In & Join Server To View
Tom
Tom•2y ago
alright
Unknown User
Unknown User•2y ago
Message Not Public
Sign In & Join Server To View
Tom
Tom•2y ago
im jhust amazed that this can be so confusing
Unknown User
Unknown User•2y ago
Message Not Public
Sign In & Join Server To View
Tom
Tom•2y ago
right. and thats close to what i have. the reason i have the useMemo() is because props.chips changes every time
Unknown User
Unknown User•2y ago
Message Not Public
Sign In & Join Server To View
Tom
Tom•2y ago
and thats what it does which is the problem
Unknown User
Unknown User•2y ago
Message Not Public
Sign In & Join Server To View
Tom
Tom•2y ago
props.chips comes from this code 1 sec, while i dig it up from a pervious attempt
Unknown User
Unknown User•2y ago
Message Not Public
Sign In & Join Server To View
Tom
Tom•2y ago
{overlayData.players.map((playerInfo, i) => {
const isLeft = i === 0;
const chips =[playerInfo.name];
if (...) chips.push("test 1");
if (...) chips.push("test 2");

...

<InfoChipContainer
heightPx={20}
chips={chips}
/>
{overlayData.players.map((playerInfo, i) => {
const isLeft = i === 0;
const chips =[playerInfo.name];
if (...) chips.push("test 1");
if (...) chips.push("test 2");

...

<InfoChipContainer
heightPx={20}
chips={chips}
/>
so this is the parent component and every time the parent rerenders its gunna rerun this map function and so every time it reruns the map function its gunna make a new chips array which when it gets used in the useEffect() will look like the deps have changed
Unknown User
Unknown User•2y ago
Message Not Public
Sign In & Join Server To View
Tom
Tom•2y ago
cause (1) its inside the return value of a larger component. (2) its derived state which everyone says is #bad
Unknown User
Unknown User•2y ago
Message Not Public
Sign In & Join Server To View
Tom
Tom•2y ago
i need the ifs and pushes cause sometimes the data for the chips wont exist
Unknown User
Unknown User•2y ago
Message Not Public
Sign In & Join Server To View
Tom
Tom•2y ago
well thas why i was using ... to get a list of just the strings and use that
Unknown User
Unknown User•2y ago
Message Not Public
Sign In & Join Server To View
Tom
Tom•2y ago
ill give it a shot. im gunna try to make a simpler, more codepen-able version and then at least it should be easier to ask people
Unknown User
Unknown User•2y ago
Message Not Public
Sign In & Join Server To View
Tom
Tom•2y ago
but thats derived state. thats why i was using memo instead that should have done the sma thing usememo*
Unknown User
Unknown User•2y ago
Message Not Public
Sign In & Join Server To View
Tom
Tom•2y ago
the docs all talk about doing it fir performance reasons, but really what it does is short circuits rerenders
Unknown User
Unknown User•2y ago
Message Not Public
Sign In & Join Server To View
Tom
Tom•2y ago
ill start with that and give it a shot
Daryl
Daryl•2y ago
If you can share a reproduction of your problem (codesandbox, codepen, etc.), it would be easier to find help.
Tom
Tom•2y ago
yeah. im gunna make one tonight. i guess this is my excuse to figure out how codepens work
Unknown User
Unknown User•2y ago
Message Not Public
Sign In & Join Server To View
Tom
Tom•2y ago
yeah well im gunna try to simplify the code in the process anyway so its easier to debug and if it doesnt work out when i try it that way ill at least be able to post and complain about it
Unknown User
Unknown User•2y ago
Message Not Public
Sign In & Join Server To View
Want results from more Discord servers?
Add your server