loop-payments / react-router-relay

Relay entry point integration for react-router
https://www.npmjs.com/package/@loop-payments/react-router-relay
23 stars 3 forks source link

Queries are disposed and not reloaded in some cases #15

Open steinybot opened 6 months ago

steinybot commented 6 months ago

There are some cases where queries are being disposed of and not being reloaded.

The most likely is:

  1. A parent entry point point route with an errorElement
  2. A plain child route object
  3. Throw an error in the child
  4. Error gets handled by the errorElement
  5. Parent entry point is unmounted and the query is disposed
  6. Click back or a link that goes back to the parent or child
  7. Those loaders are not rerun so the parent is rerendered with the disposed query

This example reproduces it: https://github.com/steinybot/react-router-relay/tree/examples/todo-nested/disposed-queries/examples/todo-nested

steinybot commented 6 months ago

I've asked some questions related to this in https://github.com/remix-run/react-router/discussions/11328

steinybot commented 6 months ago

There is a workaround which is to put this in the error boundary:

  const revalidator = useRevalidator();
  useEffect(() => {
    revalidator.revalidate()
  }, []);
steinybot commented 6 months ago

Oh need to be careful with that one. It can easily lead to infinite render loops. It works in the todo-nested example but not in my app.

steinybot commented 6 months ago

This workaround is much better:

  // It only runs on unmount. The descendents will have unmounted first and disposed of any queries.
  useEffect(() => () => {
    function hasDisposedQuery() {
      for (const match of matches) {
        if (isObject(match.data)) {
          const data = match.data
          if ('queries' in data && isObject(data.queries)) {
            const queries = data.queries
            for (const key in queries) {
              const query = queries[key];
              if (isObject(query) && 'isDisposed' in query) {
                return query.isDisposed === true
              }
            }
          }
        }
      }
      return false
    }
    if (hasDisposedQuery()) {
      revalidator.revalidate
    }
  }, []);

You put this in the errorElement on your root route.