pmndrs / react-spring

✌️ A spring physics based React animation library
http://www.react-spring.dev/
MIT License
28.2k stars 1.19k forks source link

Animated component breaks formatted string #1660

Open alextes opened 3 years ago

alextes commented 3 years ago

🐛 Bug Report

I have an <animated.p>. It gets passed a formatted string from Intl.NumberFormat with signDisplay: "always". This means a + is prepended. This plus gets dropped by the component 🙈 .

I've logged the strings as they get passed, it never omits the plus. Yet, after the animation is finished, the component with its child string gets rerendered for whatever reason it seems, and the plus disappears.

I'm pretty stumped. As it stays on the minus side I was thinking maybe it gets parsed, recognized as number, re-stringified, and that's how it disappears. Has to be something like it, and yet, even prepending a letter to the passed string to break any presumed parsing doesn't change anything.

To Reproduce

Expected behavior

strings stay formatted the way they're passed.

Link to repro (highly encouraged)

https://codesandbox.io/s/happy-breeze-k5b37?file=/src/App.js

If you'd like to skip the repro, here's what the following logs:

<animated.p className="-mb-2">
{growthRateA.to((n) => {
  console.log(
    "gauge val",
    growthRate,
    n,
    percentChangeFormatter.format(n)
  );
  return `${percentChangeFormatter.format(n)}`;
})}
Details ``` gauge val -0.011 -0.011 -1.1% gauge val 0.031 -0.011 -1.1% gauge val 0.031 -0.010121548193517607 -1.0% gauge val 0.031 -0.00857916774249765 -0.9% gauge val 0.031 -0.005950740117543118 -0.6% gauge val 0.031 -0.0029392530728557277 -0.3% gauge val 0.031 0.0030268882813645867 +0.3% gauge val 0.031 0.0043572052262135326 +0.4% gauge val 0.031 0.008124811754838784 +0.8% gauge val 0.031 0.012428674047926122 +1.2% gauge val 0.031 0.015282572633774496 +1.5% gauge val 0.031 0.019405160906445458 +1.9% gauge val 0.031 0.022080519063728186 +2.2% gauge val 0.031 0.02389470716540138 +2.4% gauge val 0.031 0.026299391624859 +2.6% gauge val 0.031 0.028825197159883555 +2.9% gauge val 0.031 0.030074829158552547 +3.0% gauge val 0.031 0.031120467901732316 +3.1% gauge val 0.031 0.032136375095277364 +3.2% gauge val 0.031 0.03324458904176794 +3.3% gauge val 0.031 0.03341674944892716 +3.3% gauge val 0.031 0.03379429059057049 +3.4% gauge val 0.031 0.034034462926820684 +3.4% gauge val 0.031 0.03402336228008903 +3.4% gauge val 0.031 0.033967395783605976 +3.4% gauge val 0.031 0.0338198482844161 +3.4% gauge val 0.031 0.033655547240881566 +3.4% gauge val 0.031 0.03323786042677272 +3.3% gauge val 0.031 0.033175304905596906 +3.3% gauge val 0.031 0.032903915378158954 +3.3% gauge val 0.031 0.03263141011617155 +3.3% gauge val 0.031 0.03235126611329709 +3.2% gauge val 0.031 0.03210147120371028 +3.2% gauge val 0.031 0.03187080596101644 +3.2% gauge val 0.031 0.03166233598676437 +3.2% gauge val 0.031 0.031467693381792144 +3.1% gauge val 0.031 0.03130912302727817 +3.1% gauge val 0.031 0.031174775466065223 +3.1% gauge val 0.031 0.031057804428564314 +3.1% gauge val 0.031 0.030965058583005904 +3.1% gauge val 0.031 0.030901156456320494 +3.1% gauge val 0.031 0.03085035087798566 +3.1% gauge val 0.031 0.030813362199849245 +3.1% gauge val 0.031 0.030792923903493395 +3.1% gauge val 0.031 0.030781616531037656 +3.1% gauge val 0.031 0.03078008319142252 +3.1% gauge val 0.031 0.030785416615148938 +3.1% gauge val 0.031 0.03079703394264702 +3.1% gauge val 0.031 0.03081304343731894 +3.1% gauge val 0.031 0.030829713834723847 +3.1% gauge val 0.031 0.030848873417013662 +3.1% gauge val 0.031 0.030870956731571284 +3.1% gauge val 0.031 0.030892771279615535 +3.1% gauge val 0.031 0.030912490436875762 +3.1% gauge val 0.031 0.03093465114884642 +3.1% gauge val 0.031 0.0309566119187836 +3.1% gauge val 0.031 0.031 +3.1% gauge val 0.031 0.031 +3.1% gauge val 0.031 0.031 +3.1% ```

Kapture 2021-08-16 at 09 14 31

Environment

Thanks for an amazing animation library! Managed to drop a full component. So easy to use 👍 .

alextes commented 3 years ago

aha, this library can animate strings with numbers, even something like rgb(0,100,0). Well, that explains. Definitely parsing the number out of the string and updating with what is thought to be equivalent but isn't.

alextes commented 3 years ago

Woohooo, found a workaround 🙌 .

I doubt anyone else is troubled by this haha, but after days and days of incredibly hard work I'm v. glad the next version of https://ultrasound.money can go live without replacing react-spring for this component (the main candidate had trouble too).

I'm now boolean flagging whether we are resting or not (onRest / onStart) and replacing the animated component with a frozen one. That still means you see a flicker as the non-animated value updates immediately but luckily!! we have animatedValue.get() in this library to get around that 🙏 .

philschoefer commented 2 years ago

I have a similar issue with formatted currencies. In my case I'm using shopify's i18n package to format a currency. When switching to german formatting, 0s get dropped.

https://codesandbox.io/s/happy-shape-gfpjw?file=/src/App.js

(change locale = 'en' to local = 'de' and you'll notice 6.920,60 turns into 6.92,6 (last number is the one showcasing the issue). The ones above I just added to verify the formatCurrency method works as expected.

Globix commented 1 year ago

Hi @philschoefer did you solved your issue? Because i'm having exactly the same problem :/

alperaktas-ets commented 1 year ago

Having the same issue as @philschoefer

For example, I have a number with a zero: 12350532 I then format it to Turkish locale string:

const [prevTotal, setPrevtotal] = useState(0);

const total = calculateTotal(values)

useEffect(() => {
    setPrevtotal(total);
  }, [total, prevTotal]);

const props = useSpring({ val: total, from: { val: prevTotal } });

return (
 <animated.p>{props.val.to((val) => val.toLocaleString('tr-TR', { minimumFractionDigits: 2, maximumFractionDigits: 2 }))}</animated.p>
)

The formatted number that gets displayed is: 12.35.532,00 but it should be 12.350.532,00 The problem does not occur when using "en-US" formatting or when I simply render the value without react-spring

alextes commented 1 year ago

To try and move things in a helpful direction, if someone is interested in tackling this bug, my uninformed guess would be that it wouldn't be very hard to pass some boolean to a spring that says don't try to parse and display my string, after formatting is done, don't touch it.

joshuaellis commented 1 year ago

I think we could look at adding a new method to the SpringValue e.g. .print which does not return an interpolation and therefore the string shouldn't need to be touched afterwards...

EDIT: to be clear, i don't consider this a bug, however I do understand the pain point.

programmablereya commented 1 year ago

I think this is a bug, and to justify that understanding, I'll point out that only the stopped values are like this. If react-spring is correctly calling the function passed to to() for the tweens, then it makes even less sense that the spring will munge it on the keyframes. I would have expected quite the opposite - that the spring might get the keyframes from the function you're given and then try to tween between them yourself.

Indeed, even that surprised me - when passing a function to transform the values of an interpolation, I don't expect the interpolation to re-interpolate the results of that function. I expect that now I'm taking matters into my own hands, and it's my responsibility to do so efficiently enough that I can run it on every frame, and to format it in a way that is accepted by whatever I'm passing the interpolation to.

I also don't expect react-spring to auto-animate between CSS values at all, at least not without me telling it I need it to do so. The documentation does not make it obvious that react-spring will do this, only using a few examples to suggest that it is capable of animating them without explaining how it does this. This I would agree is a painfully magical feature and not a bug. It can pick up color words and convert them to their colors and then animate between them! Awesome! ... Except I had no idea it could do that, so I wouldn't have tried it. Honestly, I expected this library to leave that behavior to CSS itself. So I think there's a documentation component that needs solving as well.

I may take a crack at this later; for now, I'll find some form of workaround.

programmablereya commented 1 year ago

The workaround is trivial - and, I suspect, so too shall the fix be.

import {Interpolation, InterpolatorArgs, Globals} from "@react-spring/web";
import {getAnimated} from "@react-spring/animated";

export class FixedInterpolation<Input = any, Output = any> extends Interpolation<Input, Output> {
    constructor(readonly source: unknown, args: InterpolatorArgs<Input, Output>) {
        super(source, args);
        getAnimated(this)!.setValue(this._get())
    }
}

Globals.assign({to: (source, args) => new FixedInterpolation(source, args)})

Because the AnimatedString's value is not set at first, the AnimatedString tries to interpolate what it might be from two of the same value and a number. By setting its value, the interpolation is bypassed and so too is the bug.

A more principled fix might require more knowledge of the code than I have. But I remain firm that this is a bug.

joshuaellis commented 1 year ago

Regardless of what it's been triaged as, if you think you have a solution, feel free to submit a PR to fix it 😊

programmablereya commented 1 year ago

My apologies - last night I was frustrated and overtired, and I had been wrestling with this problem for far too long before pinning it down to this library (most of my bugs were my own fault), so it came out harsher than necessary. u_u

Today I'm busy, but I'll put together a PR with a fix and some tests to confirm that it a) works and b) doesn't break anything else tomorrow, most likely!

Thanks! :)

arcturus3 commented 1 year ago

@programmablereya any updates on this? I just ran into this issue, and your fix worked for me (thanks!). If you're not interested, I'm happy to submit a PR for your fix or see if I can find any alternative solutions.

bbgaming1 commented 1 month ago

Hello, @programmablereya @arcturus3 any update on this ? :) I'm facing the same issue and I don't think there is any PR open regarding this issue ?