React-spring: React-spring with SSR and initial=null

Created on 14 Nov 2018  路  28Comments  路  Source: pmndrs/react-spring

Hi 馃憢

I'm having an issue when I'm using react-spring with server-side rendering.

I'm using Transition with initial=null, and I expect the code to be rendered on server side.

Here's the reproduction : https://codesandbox.io/s/rrk7m5kpw4

What happens is that nothing is rendered on the server-side, so when react rehydrates, there's a flash of text 馃槵

Maybe I'm doing something wrong? Thanks for your help 馃檪

SSR bug

Most helpful comment

@drcmda I've done quite a lot of testing in the past week. I couldn't set up a Sandbox, just because I needed to be able to edit your source code (I forked the repo) and link it to a local project.
Anyway, it looks promising! I just couldn't crack a couple of use cases, but I'm pretty sure you'll be able to help me with those. I'll publish the example project probably withing the weekend. 馃槈

All 28 comments

initial=null or initial={} just means it doesn't animate values on initial render, it will just jump to "enter" if it exists, but the item renders nonetheless. you could do initial={color: red} from={color: blue} enter={color: green} and it would animate red->green on first render, and then blue->green for all items that enter afterwards.

I'm not so knowledgeable with SSR, i didn't know for what to look otherwise when trying your box out.

When initial=null : on the first render , the item should have the properties of enter.

So, when you do SSR, if you set initial=null, you would want the SSR page to have the item rendered with enter properties.

What seems to happen is that the item is not rendered at all on the SSR page.

You can verify that by checking the Terminal in the codesandbox. The Terminal shows what happens on the server-side. You can see outside transition gets logged, but not inside transition.

It's always visible when you refresh the page in codesandbox. The "Hello" flashes, when one of the goal of SSR is to avoid these flashes.

I would expect the item to be rendered with initial properties when rendered on the server, so that when react is loaded on the page, the item with initial properties is already there, and it starts to animate 馃檪

Is it clearer ? 馃槵

Seems like popmotion ran into a similar issue : https://spectrum.chat/thread/af18a593-4f94-40d7-baa2-7a6cfb850ccf

Oh, i think there was a topic recently that discussed this: https://github.com/drcmda/react-spring/issues/278

SSR doesn't know browser api's like requestAnimationFrame, that makes it tough for an animation library to render out, even if it's just a frame since it will definitively want to use RAF. If you know how to get around this i could make a patch release for you.

PS. is there any way for me to test this in an easy manner? I think that theoretically i could call the updater-function once if i detect SSR without relying on RAF. That would allow safe dehydration at least.

Is it possible to render "static" items if typeof window === 'undefined' ? That would be a way to support SSR, because animations don't make sense on the server-side.

Then, when React loads on the client side, the "static" items are already there and React should be able to just replace them with "dynamic" components on its own (during the hydration).

"Static" items could be rendered with these rules :
if initial is null => render items with enter properties
if initial is an obj => render items with initial properties
if initial is not defined => render items with from properties

Would that be possible ? 馃槄

i tried, but this needs to be done by someone that understands ssr more than i do. I've tried adding a check which calls the updater immediately, but to no avail, i guess b/c spring and keyframes can only start animating after the first render phase (it calls .start() in componentDidUpdate). Keyframes (internally used by Tranition) especially is async by nature, since "slots" can be async functions, they'll always fire after the first render.

With componentWillReceiveProps being dead and componentDidUpdate as the only viable option, i wonder how other libs are doing this, coz in my mind all of them should be broken in SSR since they won't be able to amend their render output.

Here's a hacked together test, but to get there in a clean way ... no idea how tbh: https://codesandbox.io/s/ywzkwr5jnz

I've updated the codesandbox : https://codesandbox.io/s/rrk7m5kpw4

I wrap Transition in a component (TransitionSSR) that checks if the page is SSR and in that case, renders "static" items (with the rules I shared in my previous comment).

I can write a PR if you think it makes sense to add it to this library 馃檪

That's actually a nice solution right there, but i think it will get massively complex for scripted transitions, multistage and all this. So maybe a userland solution is a good way to go. Would be open to add a section for this into the documentation?

Sounds good 馃憣

I just realized react emits a warning in the console :

Warning: Did not expect server HTML to contain a <div> in <div>.

I'm not sure why though 馃I think it should be fixed before adding it to the documentation.

Hi all,

I've done some research on this issue because I've noticed the same "problem" in a small prototype I've been building.
@drcmda Maybe you could try to set the initial state on state initialization (only when doing SSR).
I've tested it by changing the state initialization of the Keyframes component to

state = {
  // props: {},
  props: typeof window !== 'undefined' ? {} : this.props.states[this.props.state],
  oldProps: {},
  resolve: () => null,
  last: true,
  index: 0,
}

and that seems to work. I've set a simple example with Gatsby using the following code

<Transition
  from={{opacity: 0}}
  enter={{opacity: 1}}
  items={true}
>
  {() => (style) => (
    <div style={style}>
      animated div
    </div>
  )}
</Transition>

The generated static html now contains
image

This is probably not the solution (there are many more use cases to keep in mind), but could this be a possible approach?

It also works for my use case 馃槺: https://codesandbox.io/s/0owq300l70 (i made the state initialization change you shared in ./spring.js).

And no react warning 馃檶

@CosmaTrix looks good, do you think it's safe to take in? this window !== undefined stuff makes me a little worried b/c many other targets don't use "window" (react-native, react-blessed, etc). Is there a better way to find out if we're in a SSR context?

@drcmda
It seems that Keyframes is using Globals.requestFrame, which is using window under the hood :
https://github.com/drcmda/react-spring/blob/f3f7768883a47225d864ecf6974b63eb21168802/src/animated/Globals.js#L4-L7

So the animation will probably not work if window is missing, so I guess it makes sense to use typeof window !== 'undefined'.

Correct me if I'm wrong 馃槃

@drcmda That would work for this particular case: transition with from and enter being defined. I've done some research in the past days and there are a few use cases that need to be tested thoroughly. For example, what happens with async states when using Keyframes directly? What happens when from is undefined? What if enter is a function and not an object? 馃槄

In general, I think it is pretty safe to change the state initialization in Keyframes to

state = {
  // props: {},
  props: this.props.from || {},
  oldProps: {},
  resolve: () => null,
  last: true,
  index: 0,
}

and remove the if (!props || Object.keys(props).length === 0) return null check. Eventually from is the where you want to start from (no pun intended 馃槃), right? And should be reflected both in the client and the server.

My plan is to create a CodeSandbox (or maybe two, Gatsby and Next.js) with as many use cases as I can think of. I'll share that here of course. What do you think?

Right, async function states, ... 馃槢

I would love to see that box. It's a tough problem. I am thinking, theoretically projecting states that could be sync --- in sync, would be fine. But then again, like you said, what if there's no from and the view depends on a certain prop that it interpolates, it would crash.

Maybe a better way would be to polyfill requestAnimationFrame into a function that calls in sync once and then self-destructs so that it cannot be called again. Not sure how, but if we have a sandbox to test this stuff out i^m sure we find a solution that suits us all.

@drcmda I've done quite a lot of testing in the past week. I couldn't set up a Sandbox, just because I needed to be able to edit your source code (I forked the repo) and link it to a local project.
Anyway, it looks promising! I just couldn't crack a couple of use cases, but I'm pretty sure you'll be able to help me with those. I'll publish the example project probably withing the weekend. 馃槈

Nice! Looking forward ...

Hi @drcmda. I've created a playground that uses a modified fork of react-spring.
So far these little changes cover quite a lot of scenarios, but I've been struggling with the immediate prop.

I've made you a contributor, in case you wanted to make any change. 馃挭

It would be great to get some help from the community too...I really hope somebody else will jump in to help.

Wanna chime in into this discussion: https://github.com/LekoArts/gatsby-starter-portfolio-cara/issues/10

I'm seeing on this particular site (which uses the Parallax) that the content doesn't get SSR. My personal site (https://www.lekoarts.de/) which uses the Spring component doesn't have this problem.

@issuehuntfest has funded $60.00 to this issue. See it on IssueHunt

@CosmaTrix i saw your invitation for react-spring-ssr, but was kind of swamped with hooks for the last time. I'll soon tranfer this repo to a GH org and build a team. Would you want to be in it? We could use your input for ssr. You'd have push rights and we could get the stuff you need working more quickly that way. @ruggedy btw has found a possible solution that would even work with async funcs, but it's still theoretical b/c we both don't have that much experience with SSR.

@drcmda No worries, I can imagine you were busy preparing v7. BTW...it looks awesome! 馃挭 I'm looking forward to giving it a try.
Sure, I'd love to be part of the team and have react-spring working SSR or with Gatsby.

The idea that John had was that we can forward initialProps to keyframes, that way even async fn's would be workable. It's already implemented with useKeyframes, but we haven't tested against SSR yet. Did you do something to avoid the requestAnimation thing?

Hey @drcmda! Sorry for the late reply, but you know...Xmas holidays! 馃槃

I agree with saying that iniitlaProps (for hooks) or from (for the regular components) should be defined to allow SSR when using async animations.

I didn't do anything with the requestAnimation thing. All the rendered components need to know is what their initial state/style is.

The main issue I've encountered is with the immediate prop: when it is set to true, the SSR rendered version has the initial style, but I would expect the final one to be rendered. For example

<Spring
  native
  immediate
  from={{ opacity: 0, y: 50 }}
  to={{ opacity: 1, y: 0 }}
>
  {({ y, ...style }) => (
  <animated.div
    style={{
      ...style,
      transform: y.interpolate(v => `translateY(${v}%)`),
    }}
  />
)}
</Spring>

outputs

<div style="opacity:0;transform:translateY(50%)"></div>

which happes to be the style defined in the from prop. But, in this case, I would expect the to to be immediately rendered. Like so

<div style="opacity:1;transform:translateY(0%)"></div>

Any thoughts about that?

Hey @drcmda,

First of all, great job with v8! 馃挭

I personally think that this issue could be closed as hooks are now production ready both in react and react-spring. I would suggest everyone to migrate to react 16.8.x and react-spring 8.x asap.

Thanks a lot!

Is this still an issue with react-spring@next? Closing for now.

@aleclarson what is the expected behavior/order of operations for Spring during SSR?

@tim-soft Good question. I guess it's supposed to render like normal, except the animations just don't execute.

One edge case involves the immediate prop, like @CosmaTrix mentioned here. We still use rAF even if immediate is true, and since rAF doesn't exist in a SSR environment, the component is rendered with from values instead.

Anyway, I don't have any time to look into SSR shortcomings currently. 馃槩

Was this page helpful?
0 / 5 - 0 ratings

Related issues

cmmartin picture cmmartin  路  3Comments

szahn picture szahn  路  3Comments

anton-g picture anton-g  路  3Comments

eDubrovsky picture eDubrovsky  路  3Comments

cklab picture cklab  路  4Comments