FormidableLabs/react-ssr-prepass

Lib not rendering components in same order

Romcol opened this issue · 7 comments

Hi!

First, thank you for the lib 👏 !

I am trying to run through the React tree to get all the elements that are need pre-rendering (API fetching). This happends successfully but the components are not rendered in the same order as with renderToString. The problem is I have unique ids (through react-uid) that should be the same between server rendering and client rendering and that are dependants on the order of rendering.

This is my code:

  try {
    await ssrPrepass(
      sheetsPrepass.collect(reactElement),
      (element, instance) => {
        if (element && element.type && element.type.getInitialProps) {
          store.dispatch(
            enqueuePrefetchedComponent(`${componentName}-${element.props.uid}`),
          );
          return store.dispatch(element.type.getInitialProps(element.props));
        }
        return Promise.resolve();
      },
    );
  } catch (error) {
    if (isRedirect(error)) {
      return { redirect: error.uri };
    }
    throw error;
  }

I have even tried to run ssrPrepass a second time (after API fetching) and the rendering is done exactly on the same order as the first time. I believe that ssrPrepass make us able to perform actions before rendering, so this shouldn't be a problem.

I don't know how I can give more information or even if this is something that should be fixed.

Thank you 🚀

Can you give me a vague overview of your app’s element tree and how the order differs in React? That’d be very helpful 🙌

I suspect it may be related to something being skipped over or to a minor difference. At least that’s what I’m hoping.

Are your UIDs order-based? And how are they being created?

Hiya 👋 do you have any reproduction or more details please? Otherwise I’ll have to close this issue for now due to inactivity

Hi, Sorry for the late reply!

I have a main React tree with which is always rendered in the same order. From those "main components" I have text clamping components which are given a unique ID (for server/client id matching). Those components are ran afterwards with react-ssr-prepass, but on server/client actual rendering they are ran earlier alongside main components.

Do you have any idea?

Not sure how I can give you more info...

Thank you 🚀

That’s unfortunately not more information to go on 😅

With this given information, I’d simply have to test all possible rendering order discrepancies, which is impossible. I’m also not aware of anything that can cause this that isn’t intended behaviour.
So without a reproduction or any specific cause I can’t actually reproduce this and drill this down

Closed due to inactivity

I see also a diff in rendering order.
I have this implementation

import React, { useState } from "react";

interface SampleProps {
  name?: string;
}

class Home extends React.Component<SampleProps> {
  // eslint-disable-next-line @typescript-eslint/no-useless-constructor
  constructor(props: SampleProps) {
    super(props);
  }

  fetchData = function (props) {
    console.log("Home -> fetchData -> fetchData", props);
    return fetch("https://google.se");
  };

  render() {
    console.log("Home " + this.props.name)
    return <h1>Hello {this.props.name}</h1>;
  }
}

const Home2 = (props) => {
  const { name } = props;
  console.log("Home2 " + name)
  return <h1>Hello, {name} <Home name={"inne" + name} /></h1>;
};

Home2.fetchData = function (props) {
  console.log("Home2 -> fetchData -> fetchData", props);
  return fetch("https://google.se");
};

const App = () => {
  const [v, sv] = useState<any>("gsl");
  console.log("App -> App")

  return (
    <div className="App" onClick={() => sv("a")}>
      <Home name="1" />
      <Home2 name="2" />
      <Home name="3" />
      <Home2 name="4" />
    </div>
  );
};

export default App;

On server

await ssrPrepass(element, (element, instance) => {
    if (element && element.type && element.type.fetchData) {
      return element.type.fetchData(element.props);
    } else if (instance && instance.fetchData) {
      return instance.fetchData(element.props);
    }
  });
// outputs
App -> App
Home 1
Home2 2
Home 3
Home2 4
Home inne2
Home inne4
renderToString(element);
// outputs
App -> App
Home 1
Home2 2
Home inne2
Home 3
Home2 4
Home inne4

@codler that does look like expected behaviour to me. Once you start suspending you have concurrent processes ongoing. So for each sub tree and path the render order in react-ssr-Prepass will be consistent to React, but given that one is tree suspends another parallel one can still continue to be rendered.

So this timing changes the render order in your case as data becomes available.

If this wouldn't be done, a Prepass overhead would be quite huge and unparallelised