React-spring: useTransition resets state on leave

Created on 24 May 2019  ·  9Comments  ·  Source: pmndrs/react-spring

Hi

I'm not sure if this is a bug, so I didn't want to tag this as such without running it by you guys first.

Check out this code sandbox: https://codesandbox.io/s/zealous-torvalds-yfwzo

If you increase the counter, then go to the 'next page' you'll see the counter on the original component is reset back to 0 on its way out.

I am building a form in my current project with the questions split over many pages. I'm using react spring for the page transitions. When I navigate to the next page, the leaving page's state is reset (which is obviously really bad UX as it looks like the data is lost).

Is there a way to not re-render the component when it's leaving and to keep its state intact?

I've scoured the internet for help obviously, but no luck.

question

All 9 comments

I've been trying to figure this out for the past two days till I finally checked the issues. It's like the <animated.element /> is replaced by a new instance onLeave including all its children.

@philipallen After tinkering with your code sandbox turns out we don't have the same issue.
Since you were declaring a pages variable without useMemo every time your state updated it was reinitialized. Also you were passing key to <Page> as a prop but you never used it in pages.

Fork of your sandbox: https://codesandbox.io/embed/modest-roentgen-845yr

Hi @jmcruzmanalo

Thanks a lot for looking into this for me.

You can ignore the key prop - react moans if you don't have a key for each child in a list within a map.

The thing is, in my project, ComponentA and ComponentB are dynamically created based on what page of the form you are in. So I have to render them in the way I used the Page component.

So if I fork your sandbox, I can easily break it by dynamically creating the components: https://codesandbox.io/s/pensive-clarke-zox7m

I'm not sure what you meant by useMemo as it doesn't appear to be used anywhere in your sandbox....Perhaps you could explain that further?

I think key is sometimes used by react-spring to keep track of what components to update/animate. Just a maybe, I don't have mastery of this yet.

I see, well that's the thing. Whenever your state (useState) updates keep in mind that const pages = [array of functions returning components] creates new instances. So going back to the issue we were having, leave is not the culprit.

useMemo can be used to stop reinitialization of components. Here is tweet by Dan. Scroll down a bit and you'll see that he uses it on a <Child1 /> and pass an array of variables that updates the memoized component if it changes.

I hope this helps you. Don't forget to close this if you think the issue was not caused by useTransition.

Hi @jmcruzmanalo

OK thank you for your help. I'll hopefully get some time later in the week to try out your suggestions. Will revert back on here and of course will close the issue if it's not useTransition's fault.

I'm facing a similar problem (transitioning a Modal and making some get calls inside it). I'm a little wary of using useMemo because of the docs:

You may rely on useMemo as a performance optimization, not as a semantic guarantee. In the future, React may choose to “forget” some previously memoized values and recalculate them on next render, e.g. to free memory for offscreen components. Write your code so that it still works without useMemo — and then add it to optimize performance.

I feel like in this case it would fall more on the second case than in the first one.
I will try to put a sandbox example later too as I don't want to make another issue.

Interesting, thanks @jromerof. So if React is telling us to 'write our code so that it still works without useMemo' then how do we get around this problem with leave re-rendering our components @jmcruzmanalo?

To be honest I'm not completely sure what was causing that. Check this out, state is persisted. I'm guessing returning a new instance of animated.div on your pages array creates a new instance of the children of animated.div. But take that with a grain of salt.

Fork, state is persisted: https://codesandbox.io/s/vibrant-saha-tegev

I recall that react-spring's hooks doesn't actually follow the usual update pattern of states. While useTransitions would update, as in animate the div, it won't necessary re-run the functional component top to bottom as would a state update. Check out the fork, notice how useEffect only fires when the animation finishes, but obviously transition is updated to change animated.div. This is why you can only use the props/styles outputted by useSpring, useTransition,...etc on animate.elementss and not on regular divs. (Tried to look for where I read this, sorry couldn't find it)

Lastly, I've never seen the pattern you used here before. Again your pages variable is an array of functions retuning components. Each state updated would make a new function returning a new component.

Hi @jmcruzmanalo. You're right - if I change to an array of components, rather than an array of function components it works. Perhaps for the reason you say in your last paragraph.

I'll close this question now. Really appreciate your help!

Was this page helpful?
0 / 5 - 0 ratings