-
Notifications
You must be signed in to change notification settings - Fork 1.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
MM-59973: fix the emoji count 0 issue when archive and unarchive chan… #8146
Conversation
can you explain the change? why the memo is not working while the ref does? more importantly, why does the count goes to 0 when archived in the original code? basically what I see happening is that you are now calculating the count on every render, this would be "fine" if we can measure the overall impact on doing so. as far as I can tell, when archiving, something is changing causing the reaction count to not match the memo and then returns 0 while unarchiving does not run the memo function again, and I want to understand the reason why that is happening and not only just changing it to a ref, which in turn it is not even needed, you could just remove the ref and calculate the value directly |
Looking at the code, the original The proposal you are making is just sticking to the original value. I wonder if adding or removing a reaction will properly show it in your solution. Have you checked the value of |
The issue that I think for the original implementation using Switching to @larkox First I thought the same, but when I checked the value for |
Thanks for the explanation @Rajat-Dabade . I have to admit this component is complex, and I struggle to grasp everything that is happening. I see some "weird things" but not sure if they are intended or not. For starters, what I already said, the Also, what I don't get from your explanation, is how having a numberHeight of 0 will set the numbers to 0. I would expect some visual artifact, but not the numbers changing to 0. Again, I may be misunderstanding part of the component. Also, |
@larkox, Thanks for the feedback. So the What I think is each digit's |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The count did not check after archiveing a channel on mobile.
Tested on iOS and Android device.
Unrelated to the problem here, but I'm looking at this component and it is kinda concerning at how many Views this component will be generating. If the number is That many views to make 1 animated number is quite a lot. If you have 10 emojis, 80 x 10 = 800. If you have 100 emojis (I hope nobody ever have 100 emojis attached to a post), it'll be 8000. Can I make recommendation to have community re-write this? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think there could be a few cleanup in this component.
I've expressed a major concern with this component, but probably out of scope for this PR/ticket.
if (typeof prevNumberersArr[index] !== 'number') { | ||
return new Animated.Value(0); | ||
} | ||
const animations = React.useRef( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: should useRef be added into import on line 4 instead of using via namespace?
Could also clean up the rest of the react hook functions (useEffect, useState, etc)
if (typeof prevNumberersArr[index] !== 'number' || numberHeight === 0) { | ||
return new Animated.Value(0); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
isn't numberHeight always 0 until setButtonLayout is being called? So this if block will return true thus animations
will be an array with all the elements having the value of new Animated.Value(0)
, I think?
If this is the case, could just make it
useRef(numberStringToDigitsArray.fill(new Animated.Value(0));
I believe the useEffect on line 54 is what updates the value depending on the number of the digit.
@larkox is correct, the useMemo will be triggered every time the component re-renders since the dependency is an object (array). If you switch that to a string But the useMemo has a few dependencies missing:
I think. xD |
@rahimrahman I see, If this is the case then I guess rewriting the component will make more sense than updating it. |
@Rajat-Dabade In my opinion, the fix is something that is needed soon-ish, whereas the re-write is my recommendation, and might be added in the roadmap in the future, or might not be done at all. The way it is working now is a concern of mine, because it might impact performance, but we will need more proof. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree with @rahimrahman about the re-write, but not really sure if that is needed as today is working with this code and does not seem to be a major problem.
other than the comments that already were made, this lgtm
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just a minor improvement from my side.
8940ecc
to
9a0b74a
Compare
@Rajat-Dabade I use AnimatedNumber component as a topic of my presentation a few weeks ago, about how many Views this component is generating and the potential impact on performance. The demo was done using a new Expo app. I couldn't use your changes because it was crashing, and I didn't have the time to figure out why (at that time) and I completely forgot about it after that, but seeing this PR discussion started up again, it reminded me that there was an issue and glad you were able to fix crash. But, since I had that Expo app created already and your recent changes fixed the crash, I ran a comparison between the original and your changes today. See the two videos: original.mp4after-fix.mp4You can definitely see the lag on after-fix video. While the demo in these videos are not a real use case of what we have for this component, I think it's indicative of a potential performance impact. If you choose to continue with useRef, perhaps you could figure out why it's causing a lag in this stress test environment. Since useMemo() is the faster alternative, I played around with it and found out the fix is really simple, which is adding all the missing dependencies in the useEffect. React.useEffect(() => {
animations.forEach((animation, index) => {
...
});
}, [animationDuration, animations, easing, numberHeight, numberStringToDigitsArray]); // <=== as far as I can tell, adding missing dependencies here fixes the original problem But another problem with the dependencies above, is Another issue is the use of useMemo and useEffect is redundant. I combined the two into: const animations = useMemo(() => {
const _numberStringToDigitsArray = Array.from(
animateToNumberString,
Number
);
const prevNumberString = String(Math.abs(prevNumber));
const prevNumberersArr = Array.from(prevNumberString, Number);
return _numberStringToDigitsArray.map((__, index) => {
const animationHeight = -1 * (numberHeight * prevNumberersArr[index]);
const animation =
typeof prevNumberersArr[index] === "number"
? new Animated.Value(animationHeight)
: new Animated.Value(0);
Animated.timing(animation, {
toValue: -1 * (numberHeight * _numberStringToDigitsArray[index]),
duration: animationDuration || 1400,
useNativeDriver: true,
easing: Easing.elastic(easing || 1.2),
}).start();
return animation;
});
}, [
animationDuration,
easing,
numberHeight,
animateToNumberString,
prevNumber,
]); This is just a quick refactor, and I think further improvement can be made. As an example, moving from 20 => 21, the first digit Also, notice that the dependencies are all primitive (so it can do a shallow comparison). I moved all of the conversion into array inside the useMemo block. I also change the props easing to a number instead of a function. I didn't see easing being passed in any of the AnimatedNumber component usage, so this change should be safe (but please check to make sure). I know my respond here is pretty lengthy, but I hope it make sense. Also, while it's not yet a common practice here at MM (even though we just talked about this last week during dev meeting), I encourage creating a proper component unit test before any refactoring, to ensure the changes doesn't break any other functionalities of the AnimatedNumber component. |
@rahimrahman, Thanks for the detailed feedback and for highlighting the potential performance issue with the number of I've implemented your suggested changes, including:
By introducing these changes, it should now only animate the digits that have actually changed. This reduces the number of unnecessary animations, and I expect it to help with performance, especially in edge cases where digits remain constant during number transitions. I've also updated the Could you please take another look and let me know if there is anything I have left? It's a good learning 🚀 . Thank you. |
// Skip animation if the current and previous digits are the same | ||
if (prevNumberersArr[index] === digit) { | ||
return new Animated.Value(-1 * (numberHeight * digit)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
-
Could this cause issues when prevNumber = 99 and animateToNumber = 100? In this case, numberStringToDigitsArray = [1, 0, 0], but prevNumberersArr = [9, 9]. On the first digit, prevNumberersArr[0] !== 1, so the condition will fail. Could this behavior cause issues during a rapid animation test (e.g., moving +1 every 100ms)?
-
(NIT) This comment seems redundant since the logic is already clear from the code itself
if (prevNumberersArr[index] === digit) {
Following the DRY principle, you could remove it. However, I think a comment explaining the next line
// skipping any animation by just returning the current "position"
return new Animated.Value(-1 * (numberHeight * digit));
...could add value.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, this condition could indeed cause an issue when transitioning from 99 to 100. The problem arises because we skip the animation when the digits are the same. For example:
prevNumberersArr = [9, 9]
numberStringToDigitsArray = [1, 0, 0]
When comparing the first digit (1
vs 9
), the condition prevNumberersArr[0] === digit
will fail, which is expected, and the animation will proceed for the first digit. However, for the second and third digits, the condition prevNumberersArr[1] === digit
will compare 9
(previous) to 0
(current), which is not equal, so those digits will still be animated, which is correct.
The issue is not necessarily in this block itself, but the logic could behave unexpectedly if we are rapidly changing values (e.g., every 100ms). Skipping digits could lead to an incorrect visual transition, especially when the change is rapid, such as going from 99
to 100
.
To ensure the conditions for rapid changes are avoided we can add an extra condition for checking whether the length of the prevNumberersArr
and numberStringToDigitsArray
are equal or not which will ensure that the animation isn't skipped due to rapid changes.
}); | ||
}, [animateToNumber, animationDuration, fontStyle, numberHeight]); | ||
}, [animateToNumberString, prevNumber, numberHeight, animationDuration, easing]); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
prevNumber
is not a dependency of this useMemo but prevNumberString
is. I know they are both related, but for the sake of not triggering "exhaustive-deps" warning (it is currently disabled), I recommend using the correct dependency.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated the dependency.
return new Animated.Value(0); | ||
const animations = useMemo(() => { | ||
if (numberHeight === 0) { | ||
return []; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Q: would it be safer to return
return numberStringToDigitsArray.fill(new Animated.Value(0));
just to protect from future regression?
Could transform.translateY: undefined
translateY: animations[index],
cause any issue if index is out of bounds due to animations being empty.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, but I think numberStringToDigitsArray
is a number
array and cannot be filled with Value
type. Maybe map
can work here to return the new array filled with Animated.Value(0)
.
* reversed from all useMemo to useMemo + useEffect * tried to include everything into useMemo was an anti-pattern * moved back to useMemo + useEffect * previousNumber and animateToNumber comparison has some flaw when dealing with difference length between the two * simple fix * fix more silly mistakes * move _previousNumberString to constant
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
thank you Rajat
/update-branch |
Summary
The PR fixes the bug count to be 0 for every reaction in the centre channel when archive and unarchive channels.
Ticket Link
https://mattermost.atlassian.net/browse/MM-59973
Device Information
IOS, Andriod
Screenshots/ Screen Recording
Before:
Screen.Recording.2024-08-13.at.2.03.15.PM.mov
After:
https://github.com/user-attachments/assets/5f07acb0-6d65-4228-9966-378e905ff725
Release Note