pmndrs / react-spring

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

SpringValue.map #1614

Open LoganDark opened 3 years ago

LoganDark commented 3 years ago

Proposal

react-spring's desire to completely avoid rendering has been causing tons and tons of problems for me ever since I tried adopting it. The seemingly opaque SpringValue datatype can only be used in very specific places due to the way animated works. I can't do things like change display to none only when a spring value is 0 because my component does not get re-rendered. It's so annoying and restrictive and I have to spend hours with each component figuring out how to coax the behavior I want out of the spring.

Perhaps one way to help with this would be to introduce a map method on SpringValue which would return another SpringValue that depends on the first one, allowing me to insert it into the right slots to get animated to recognize it, but process the values that reach the DOM. This would allow me to do things like hide an element when it is not visible (visibility value is 0).

<Spring to={...}>
    {({value}) =>
        <animated.div style={{display: value.map(v => v === 0 ? 'none' : undefined)}}>...</animated.div>
    }
</Spring>

This will solve some of the issues with the current system without react-spring having to move to another.

Rationale

The perfect solution that I have come up with is a component that re-renders every frame by using window.requestAnimationFrame and detecting when the spring values change. Of course, this is the exact OPPOSITE of what this library is designed to do, but - it solves every single problem I had with spring, and allows me to use the values however I like. I call it the SpringSolidifier because it turns the SpringValues into actual values that can be used. It uses a render prop to return the "solidified" values object. Additionally, it allows me to completely unmount the element when it is fully invisible. This is currently not possible with react-spring to my knowledge. Due to this, I'm no longer reliant on the quirks of animated - springs become usable anywhere in my application, for any purpose, even CSS properties or conditional rendering or what-have-you.

However, since that component is the complete opposite of what this library currently does, I think a map function would fit the project better. It would allow react-spring to keep the updates out of React, but also allow the consumers of the library to control those updates. Right now react-spring only has the former.

chrissantamaria commented 3 years ago

Perhaps I'm misinterpreting your issue, but it sounds like you're describing interpolation which is already supported by react-spring. For example, consider the following snippet:

<animated.h2
  style={{
    opacity: spring.opacity,
    display: spring.opacity.to((v) => (v === 0 ? "none" : undefined))
  }}
>
  Edit to see some magic happen!
</animated.h2>

This should achieve the behavior you're describing of creating a dependent property based on an existing SpringValue. Here's a full CodeSandbox for reference.

It would be great if you could provide a reproduction of your own to help narrow down exactly what the issue is you're running into.

joshuaellis commented 3 years ago

Thanks for the proposal!

It would be great to see an example of how react-spring is not fulfilling your needs, maybe a small code-sandbox?

I'm not sure if rendering your component at a potential of 60fps is the right thing to do though? In large complex apps I can see this being a massive pitfall. Also @chrissantamaria's comment could be very insightful for you.

LoganDark commented 3 years ago

Perhaps I'm misinterpreting your issue, but it sounds like you're describing interpolation which is already supported by react-spring. For example, consider the following snippet:

<animated.h2
  style={{
    opacity: spring.opacity,
    display: spring.opacity.to((v) => (v === 0 ? "none" : undefined))
  }}
>
  Edit to see some magic happen!
</animated.h2>

This should achieve the behavior you're describing of creating a dependent property based on an existing SpringValue. Here's a full CodeSandbox for reference.

That does appear to be what I'm describing, yes. I've never heard the word "interpolation" used to describe mapping before, and the documentation page doesn't really describe anything. Perhaps I could turn this into a tracking issue for improving the docs...?

joshuaellis commented 3 years ago

the documentation page doesn't really describe anything. Perhaps I could turn this into a tracking issue for improving the docs...?

Here's the current docs on this, if anyone would like to submit a PR to add more to this page that would be great, i'm currently migrating the docs to this repo instead it's stand alone repo react-spring.io

LoganDark commented 3 years ago

I'm not sure if rendering your component at a potential of 60fps is the right thing to do though? In large complex apps I can see this being a massive pitfall.

Yeah, I can see why the library was designed this way. Regenerating the whole tree and running reconciliation is still far more complex than surgically modifying some values in the DOM. The surgical method is just somewhat difficult and non-obvious to work with, especially when you're working in React...

Also @chrissantamaria's comment could be very insightful for you.

Already replied :P