Jump to content
Search Community

Element snapping to new grid container with Flip (React)

noahtrotman test
Moderator Tag

Go to solution Solved by GreenSock,

Recommended Posts

Hi,

 

I am trying to animate a box between two containers within a grid layout using the Flip plugin. I've mainly followed the React Flip demo on GreenSock's codepen. Currently its moving containers but its snapping to the other container and resetting its rotation. I have the animation at 4 seconds and it's still snapping and I am unsure why. To simplify things I've put the Flip trigger in a button rather than call it on the onComplete of the rotation tween.

 

Sandbox: https://codesandbox.io/s/withered-framework-k6fwm5?file=/components/Main.js:1778-1791

 

Order Sequence:

1. blue and yellow box to rotate

2. after completed. click flip

3. blue box will snap to bottom left corner

 

As it's only intended to move one way, you'll have to refresh the sandbox to start it over.

 

I appreciate any help.

 

Regards,

Noah

 

GreenSock React Flip Codepen: 

See the Pen OJzvOBv by GreenSock (@GreenSock) on CodePen

Link to comment
Share on other sites

I'm not a React guy, but from a quick glance my guess is that the elements you're trying to flip are getting re-rendered by React such that the Elements are completely different instances. So Flip can't find a matching element in the state object. In other words, when you do Flip.getState() on let's say elementA, it records that of course. Then my guess is that you do something that causes React to dump elementA and swap in an entirely new element instance (even if it looks the same - it's a new Element instance) - let's call it elementB. Then when you do your Flip.from() and feed it the targets, Flip looks at the targets (which in this case is elementB) and tries to find the matching state data in the state object you passed in, but it says "I can't find anything in there that's associated with elementB...so let me check for matching data-flip-ids...nope, didn't find a match so I guess I'll skip it". You're trying to tell it "for elementB, I want you to use the state data from elementA". 

 

All you need to do in that case (if you're flipping between two completely different Element instances) is make sure they have matching data-flip-id attribute values. Does that help? 

Link to comment
Share on other sites

Hi Jack,

 

Given the behaviour, that kinda makes sense. Would there be a way to test it? I looked in the dev tools and when the element in moved in the dom all the class names stay the same (since nextjs adds unique class names to each dom node). I don't think data-flip-id would help me in this case as I am just trying to move the one instance (blue box) to the corner container

Link to comment
Share on other sites

5 minutes ago, noahtrotman said:

I don't think data-flip-id would help me in this case as I am just trying to move the one instance (blue box) to the corner container

Yeah, you'd just give it a unique ID. It really should be that simple. Literally, just make sure that element has something like data-flip-id="my-id" (attribute). 

 

I just tested my theory about the element instances not matching and I was correct. React is definitely dumping the old element and creating a new one. 

 

Another problem I see in your demo is that you're recording the state BEFORE you do your rotation animation, thus when you flip, it'll have stale data. The proper way to do a flip is to capture the state immediately before you do the state change. So capture the state, then do the state change (right away), then Flip.from() the recorded state. That way you don't use stale data. 

 

Maybe this will help a little (keep in mind I'm not a React guy): 

https://codesandbox.io/s/nervous-nobel-xo0e9n?file=/components/Main.js

Link to comment
Share on other sites

Hi Jack,

 

I read through your sandbox a few times and understand how your just appending the data-flip-id of the original element (as you said ElementA) and appending it to ElementB so they hold the same id and remove the jump.

 

To clarify the original problem, react is dumping the original element (ElementA) and thus causing the new component to be different (ElementB). The data-flip-id's are now different (at least different instances of each other as they both will read the same id before and after in the console) and that is causing the jump. Correct?

 

15 hours ago, GreenSock said:

Another problem I see in your demo is that you're recording the state BEFORE you do your rotation animation, thus when you flip, it'll have stale data. The proper way to do a flip is to capture the state immediately before you do the state change. So capture the state, then do the state change (right away), then Flip.from() the recorded state. That way you don't use stale data. 

As I'm still getting the hang of Flip in React, I don't quite understand how I would get the state after the rotation. Isn't the Flip.getState being called when the function is triggered and thus after the rotation?

 

This also makes me question if the two are related. That is, the rotation being reset and the Flip position snapping. So far I tried a few things with no luck, including using refs, different classes, context. Looking at both the GreenSock React Flip Codepen and mine, one thing that I can see is that I'm using styled-components as all the Flip methods seem to be the same for both. I'll try to see if this is perhaps the issue. 

Link to comment
Share on other sites

15 hours ago, noahtrotman said:

To clarify the original problem, react is dumping the original element (ElementA) and thus causing the new component to be different (ElementB). The data-flip-id's are now different (at least different instances of each other as they both will read the same id before and after in the console) and that is causing the jump. Correct?

Not exactly. I already tried to explain exactly what's happening earlier. Not sure how to say it differently, but yes it boils down to the fact that React is completely recreating that element (elementA). In order for Flip to work its magic, it has to take the targets of your Flip.from() and find the matching data in the "state" object that you captured earlier (the one that has all the position/size data). In your case, it's sorta like doing this: 

let state = Flip.getState(elementA);

// then later...
Flip.from(state, {
  targets: elementB // <- uh oh
});

So Flip is like "okay, I'm supposed to Flip elementB from its previous state...let me look up the position/size data in the state object. Doh! I can't find any because there's no matching elementB in there. I only see data for elementA, so I guess I'll just skip it." 

 

The entire point of data-flip-id is to provide a way for you to associate to DIFFERENT elements which is precisely what we must do here (elementA and elementB). So the super-simple solution is to assign them the same value in that attribute. Done. It's really that simple. In my fork of your Sandbox, though, I had no idea how to set an attribute on that element, but here's a fork where I figured it out: 

https://codesandbox.io/s/trusting-wind-xekmwj?file=/components/Main.js

 

Notice I'm just setting the data-flip-id to the word "hope" but of course you make it whatever you want. The point is that React is rendering it the same, so even when React re-renders an entirely new instance that doesn't match what was in our Flip.getState(), it will still Flip okay because it has the same data-flip-id value. 

 

15 hours ago, noahtrotman said:

Isn't the Flip.getState being called when the function is triggered and thus after the rotation?

Yes, I think I misspoke on your earlier Codesandbox. One other issue was that you set simple: true, but you shouldn't do that if you've got rotation applied to anything. By "simple", it basically means "I promise, this is just a simple positioning thing, no fancy rotation, skew, nesting inside another element with transforms, etc."

 

Does that clear everything up? 

Link to comment
Share on other sites

Hi Jack,

 

Yeah I think this clears everything up. Thank you for breaking it all down and allowing me to better understand what's going on. I really appreciate all your help! 

 

I'm still doing a lot of trial and error to see what's causing it but have a hunch it's styled-components. I'll need to read through their docs to better understand what's going on. In the mean time as a temporary fix I've used a .from() tween so it doesn't rotate back on itself as the two instances will share the same data-flip-id.

https://codesandbox.io/s/intelligent-wright-mhb7t9?file=/components/Main.js

Link to comment
Share on other sites

3 hours ago, noahtrotman said:

I'm still doing a lot of trial and error to see what's causing it but have a hunch it's styled-components. I'll need to read through their docs to better understand what's going on.  In the mean time as a temporary fix I've used a .from() tween so it doesn't rotate back on itself as the two instances will share the same data-flip-id.

I keep getting the feeling that we're not quite connecting :)

 

I don't think you need to switch to a .from(). I don't think styled-components are inherently to blame. There are two simple problems: 

  1. You didn't have a data-flip-id defined that would allow Flip to link the two elements that you were trying to flip (elementA and elementB). Just declaring that one thing...one value...is the key. 
  2. You set simple: true when you shouldn't (because you were rotating). 

Perhaps I'm missing something that caused you to think .from() was necessary or that styled-components were problematic. But when I read your posts I keep getting the sense that you **almost** understand what the fundamental problem was, but not quite. 🤷‍♂️

Link to comment
Share on other sites

Hi Jack,

 

So I understand why the data-flip-id needs to be used. I guess I was confused from the gsap tutorial on the GreenSock youtube where the orange and purple rectangles are flipping between containers and there doesn't appear to be a data-flip-id. I then decided to remove the data-flip-id from the react gsap codepen above and things make sense.

 

In regards to styled-components. I guess it because you said React is completely recreating the element (elementA). I thought this was from my code and not React itself. Looking at all the react gsap sandboxes with Flip and comparing them to mine, everything seemed good so styled-components was the last thing in my process of elimination.

 

The only part I'm still confused about and also the reason I used the .from() tween is the box rotating back to 0deg. My intention is to have the boxes rotate to 45deg and then stay like that. Since a new instance of the element is being created when the Flip happens, it won't rotate back to 0deg with the .from() tween. Is this approach unnecessary?

Link to comment
Share on other sites

  • Solution
3 hours ago, noahtrotman said:

I guess I was confused from the gsap tutorial on the GreenSock youtube where the orange and purple rectangles are flipping between containers and there doesn't appear to be a data-flip-id.

That's because the elements are all consistent. There's no need for a data-flip-id when you're using the same elements. In your case, your app is literally creating an entirely new element instance. It would be like if in the GreenSock (colored boxes) demo, when you clicked to filter the list, every element got trashed and completely recreated (it doesn't work that way, I'm saying IF it did). 

 

7 hours ago, noahtrotman said:

The only part I'm still confused about and also the reason I used the .from() tween is the box rotating back to 0deg. My intention is to have the boxes rotate to 45deg and then stay like that. Since a new instance of the element is being created when the Flip happens, it won't rotate back to 0deg with the .from() tween. Is this approach unnecessary?

It's the same exact issue - the way you've set things up causes React to completely re-create (and re-render) that element (as a new element). So let's say you start out with no rotation/transform applied to that element, and then you use GSAP to animate it to rotation: 45. Great. It'll have an inline style like transform: rotate(45deg). But then when you click "Flip", your app is causing React to dump that old element (the one with the inline style of rotate(45deg)) and it's totally recreating it without that inline style. This has absolutely nothing to do with GSAP. It's totally React (or whatever 3rd party tools you're using). That's why when you do the Flip, it's animating to a non-rotated state. GSAP is doing exactly what you told it to do - it's matching that new state of the element (which is actually a whole new element). 

 

This is part of why I dislike things like React. It gets confusing when it totally re-creates entire elements and you don't realize it (because they're supposed to look the same). With the GreenSock Flip demo, the elements are all consistent. None of them evaporates or gets completely recreated as a new element. Clean, simple, clear. The React layer is what's creating the confusion for you here. 

 

So if you prefer to use that .from() approach, fine. But you certainly don't need to. You just need to understand what's happening. If, for example, you animate to a rotation of 45 degrees, you should make sure that React maintains that in the new state. That's more of a React question. 

 

Does that clear things up? 

Link to comment
Share on other sites

  • 2 weeks later...

Updating this thread for anyone that comes across it. I did some further research on maintaining state with React.

 

tl;dr its not possible as its just the way React is. If you're using gsap Flip with React, you need data-flip-id so it can calculate the difference between the two instances of components since state will always be reset.

 

Essentially, React preserves the state of a component while it stays in the same position of the dom, however once you move the position, the state will reset and a new instance of the component will be created with a different tree. Same happens even if you are putting it back to the same position, say a toggle. The React docs article on 'preserving and resetting state' explains it quite well.

 

Readings on React State & Re-rendering:

https://beta.reactjs.org/learn/preserving-and-resetting-state

https://www.joshwcomeau.com/react/why-react-re-renders/

https://www.youtube.com/watch?v=7YhdqIR2Yzo&t=768s

 

You may look into reparenting with React, which is quite difficult. On the other side there's React portals. Although this wouldn't really work in this use case if you needed to get around Reacts re-render loop this would be a plausible solution. Side note, not sure if this is possible with gsap but that's for a different time/ thread. Basically think of portals as a way to 'portal' a component into another part of the virtual dom and the actual dom will show where you want the component to be. If you want the component to be hidden from the start, and portal it to where you want, look at reverse portals.

 

Further research on React portals:

https://reactjs.org/docs/portals.html

https://www.youtube.com/watch?v=IgJcK0SbiLM

https://github.com/tajo/react-portal

https://github.com/httptoolkit/react-reverse-portal

Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...