#defer resolving to empty object

1 messages · Page 1 of 1 (latest)

strange wedge
#

I'm using defer and I've confirmed my data is resolving in the loader. My fallback ui renders and I can see that the page is waiting for the promise to resolve on the backend. When the promise resolves, the fallback gets swapped out for my actual component, however, the resolved object is empty.

I can confirm that the promise resolves to the expected, simple object on the backend, and there are no errors being triggered. Why does the same promise simultaneously resolve to an empty object on the frontend? I'm following the example posted in these docs: https://reactrouter.com/en/main/guides/deferred

I should also add that if I click away to another page (using clients side routing) and then click back to this page, the data renders as expected, which tells me this has something to do with how that data is streamed to the client.

I'm building my app using react-router v6.9.0, vite, and react w/ renderToPipeableStream().

strange wedge
#

I came across this hydration data object, "user" is the object I'm trying to resolve : window.__staticRouterHydrationData = JSON.parse("{\"loaderData\":{\"0\":null,\"0-3\":{\"user\":{}}}

dense notch
#

How are you landing on the page with the deferred data? I’ve had suspense issues with any serverside navigation or refresh but client side navigating (Form without reloadDocument) to a page with defer has worked well

strange wedge
#

This is my loader: ```export async function userSettingsLoader() {
const userSettingsRequest = query('http://localhost:3333/api/sleep', {
timeout: 3333,
mockData: mockUserData,
});

userSettingsRequest.then(result => {
console.log('REQUEST SUCCESS', result);
});

return defer({ user: userSettingsRequest });
}```

This is the shape of result when it prints out: { first: string; last: string; id: string }

This is my component:

  const userData = useLoaderData();

  return (
    <div>
      <Suspense fallback={<div>This is teh fallback</div>}>
      <Await resolve={userData.user} errorElement={<p>Error loading user data</p>}>
        {(user) => {
          return (
            <form>
              <input name='first' placeholder='first' value={user?.first} />
              <input name='last' placeholder='last' value={user?.last} />
              <input name='id' placeholder='id' value={user?.id} />
            </form>
          );
        }}
      </Await>
      </Suspense>
    </div>
  );
#

When I land on the page I see "This is teh fallback", then after a few seconds the form renders but the value of "user" is an empty object.

little sparrow
#

Try wrapping your deferred data in a promise. I think the Await tries to unwrap the promise and since you don't have one. that's causing your problem.

little sparrow
#

If I define query as:

  return new Promise((resolve) => setTimeout(() => resolve(options.mockData), options.timeout));
}```

your code works fine for me
#

It looks like you may be using remix. You might want to add that tag and give a more complete example of your code so people can help you.

thorn fossil
#

I'm building my app using react-router v6.9.0, vite, and react w/ renderToPipeableStream().

If you're using react router (and not Remix) and doing manual SSR then defer/Await won't work out of the box

#

The client has no idea about the Promise created on the server, so you need to manually "transport" the Promise over the network to the client so you can hydrate your client side React tree with a newly created Promise that will resolve when the server Promise resolves.

#

It's not a trivial concept so I'd strongly recommend using Remix if you want SSR + defer 🙂

strange wedge
#

I'm not using remix (just react-router-dom) so hopefully can get this to work

#

The client has no idea about the Promise created on the server, so you need to manually "transport" the Promise over the network to the client so you can hydrate your client side React tree with a newly created Promise that will resolve when the server Promise resolves.
@thorn fossil is there an example somewhere of how this is done?

thorn fossil
#

I'm not at my computer at the moment, but you'd have to do something similar to what Remix does. You can look in the remix-server-runtime and remix-react packages to see what they're doing if you want to head down that path

dense notch
#

It's quite a technical undertaking and you should have a strong reason not to just use Remix before building that yourself

strange wedge
#

I'm gonna take a quick look at this this morning and if needed create a task for someone on my team to come back to it later, before doing that however, I was just reviewing the docs on Deferred Data on the React Router docs: https://reactrouter.com/en/main/guides/deferred and there's no mention of needing Remix, in fact it would seem that this should just work out of the box?

thorn fossil
#

It does for client side apps, which is the primary usage of react router. Server Rendering is a separate (and much more advanced) setup. Remix is the recommended way to do SSR with React Router 🙂

thorn fossil
#

Or, said another way - Remix is the "out of the box" way to SSR React Router. createStaticHandler/StaticRouterProvider are lower level primitives that require some wiring together

strange wedge
#

Thanks @thorn fossil for the fair warnings, and for the clarification on docs. It would be great if we could avoid adding a whole framework and the overhead it would introduce to the app in order to get this one feature

#

I know it's a totally different library and it's runtime is quite different from react's but I put together a reference implementation of ssr + streaming recently with solidjs and it just worked out of the box, with just the core library and the solidjs router - which lead me to think this wouldn't require much more lift with react