Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Error: Invariant failed: .create cannot be used on server side with caching enabled #34

Closed
laggingreflex opened this issue Oct 14, 2020 · 8 comments

Comments

@laggingreflex
Copy link

Getting this error on server-side when using .create

Error: Invariant failed: .create cannot be used on server side with caching enabled

Does that mean I should disable serialization when on server? That doesn't seem to work either, it gives another error:

  const Resource = createResourceFactory(apiCall, {
    id,
    serialize: isNode ? false : ...
  });;

Error: Invariant failed: serialization cannot be disabled when using global cache

@overlookmotel
Copy link
Owner

Code example of the .create() call please. (sorry for terse reply - I'm running out the door)

@overlookmotel
Copy link
Owner

Stack trace would also be helpful.

@laggingreflex
Copy link
Author

laggingreflex commented Oct 14, 2020

Repro: https://github.com/laggingreflex/repro-react-async-ssr

Here's the .create call: https://github.com/laggingreflex/repro-react-async-ssr/blob/master/app.js#L41

Stack trace:

C:\...\repro-react-async-ssr\node_modules\tiny-invariant\dist\tiny-invariant.cjs.js:13
    throw new Error(prefix + ": " + (message || ''));
          ^

Error: Invariant failed: `.create` cannot be used on server side with caching enabled
    at invariant (C:\...\repro-react-async-ssr\node_modules\tiny-invariant\dist\tiny-invariant.cjs.js:13:11)
    at ResourceFactory.create (C:\...\repro-react-async-ssr\node_modules\react-lazy-data\dist\cjs\index.js:330:28)
    at createResource (C:\...\repro-react-async-ssr\app.js:41:29)
    at Suspended (C:\...\repro-react-async-ssr\app.js:24:40)
    at render (C:\...\repro-react-async-ssr\node_modules\react-async-ssr\lib\shim.js:66:18)
    at shimmedComponent (C:\...\repro-react-async-ssr\node_modules\react-async-ssr\lib\shim.js:85:12)
    at processChild (C:\...\repro-react-async-ssr\node_modules\react-async-ssr\node_modules\react-dom\cjs\react-dom-server.node.development.js:3090:14)
    at resolve (C:\...\repro-react-async-ssr\node_modules\react-async-ssr\node_modules\react-dom\cjs\react-dom-server.node.development.js:3013:5)
    at Renderer.render (C:\...\repro-react-async-ssr\node_modules\react-async-ssr\node_modules\react-dom\cjs\react-dom-server.node.development.js:3436:22)
    at Renderer.render (C:\...\repro-react-async-ssr\node_modules\react-async-ssr\lib\renderer.js:278:21)
undefined
internal/process/promises.js:213
        triggerUncaughtException(err, true /* fromPromise */);
        ^

[UnhandledPromiseRejection: This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). The promise rejected with the reason "1".] {
  code: 'ERR_UNHANDLED_REJECTION'

@overlookmotel
Copy link
Owner

Thanks for the repro. There's two problems here.

Firstly, .create() is intended to be used in event handlers only, not in the render phase. This is shown in the docs, although I admit it's not as clear as it should be.

Second is that, because .create() is for use in event handlers, and event handlers don't make any sense on the server side, .create() doesn't work in server side rendering - hence the error you're getting. But that is definitely not clear from the docs - apologies.

The solution is to use .use() instead of .create().

Try substituting this into your example:

function Suspended( props ) {
  const [id, setId] = useState( 0 );
  const resource = Resource.use( { id } );
  const [startTransition, isPending] = useTransition( { timeoutMs: 3000 } );

  const update = () => {
    startTransition(() => {
      resource.dispose();
      setId( id => id + 1 );
    });
  };

  return h( Suspense, { fallback: 'Loading' }, h( App, { resource, update, isPending, ...props } ) );
}

I'm not at all familiar with useTransition() or what it's meant to do. So it may be you can remove the resource.dispose() line too. This package should automatically dispose of the old resource when a new one is created, or the component is unmounted.

Also, this library is targeted at React's stable public releases - I don't know if this package will work at all with the the experimental branch, concurrent rendering etc. As mentioned in the docs for this package, SSR is not stable in React > 16.9.x (unfortunately React made some changes in 16.10.0 which completely broke SSR + Suspense). I'd be interested in hearing if it does work, so please let me know.

@overlookmotel
Copy link
Owner

NB I see in your example you have lines commented out where the resource's id is created with Math.random() and serialize() is also random. That definitely won't work. The entire point of the id and serialized request string is that they're consistent across both server and client, and this won't be the case if they're random. id and serialize() must be deterministic.

@laggingreflex
Copy link
Author

I did try .use at first, it does work, but it's not able to fully replicate the effect of useTransition for some reason.

Here's what useTransition does: https://codesandbox.io/s/jovial-lalande-26yep (from the docs: https://reactjs.org/docs/concurrent-mode-patterns.html#adding-a-pending-indicator)

The main thing to note is that when you click "Next", it loads the profile in background, and then immediately switches to it without the "Loading Profile..." text (not to be confused with the "Loading.." text on the disabled button).

I modified the above example using react-lazy-data's .use: https://codesandbox.io/s/serene-resonance-ygfvk

And here you can see that it momentarily does flash the "Loading Profile..." text, below the button, even though useTransition worked its magic and loaded the profile in background ready to be switched to, it still loads (probably re-loads) the new profile showing the Suspense placeholder momentarily.

And it's only then that I decided to use the .create method, but alas it doesn't work on server.

So that's my conundrum. Using .use works but it breaks (or rather renders useless) the Reacts useTransition. And while .create works, it breaks SSR.

Would love if you have any solution for this?

@laggingreflex
Copy link
Author

I think this may be related to incompatibility between useEffect hook and useTransition. Related issue: facebook/react#17272. I've also posted a question on SO: https://stackoverflow.com/questions/64363882/does-useeffect-not-work-with-usetansition-concurrent-api

Closing since this has gone off topic. Thanks for the response!

@laggingreflex
Copy link
Author

Found a possible solution: calling ._load() right after .use

const resource = Resource.use( { id } );
resource._load()

https://codesandbox.io/s/great-heisenberg-nthzv

@overlookmotel Is that OK to do?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants