facebook / facebook/relay

Making usePreloadedQuery throw a hard error when the environment changes will break React Router

Open
#4,623 1 comment 0 reactions 0 assignees View on GitHub
Dominant language
Rust
Stars
19k
Forks
1.9k
PR merge metrics
No merged PRs in 30d

Description

`usePreloadedQuery` will log a warning if it is passed a preloaded query that was created with a different environment than the one that is currently in context. It says that this will become a hard error in future. This seems reasonable however this is going to cause a pretty big problem when using relay with react-router.

Consider we have something like:
```



```

Where `MutableRelayEnvironment` is something like:
```
export function MutableRelayEnvironment(props: Props) {
const [environment, setEnvironment] = useState(createNewEnvironment);
return setEnvironment(createNewEnvironment())}>

{props.children}

;
}
```

That can be update elsewhere like:
```
export function CreateNewEnvironmentButton() {
const createNewEnvironment = useContext(MutableRelayEnvironmentContext);
return Create new environment
}
```

And a naive router such as:
```
export function MyRouter() {
const environment = useRelayEnvironment();
const routes = preparePreloadableRoutes(MY_ROUTES, {
getEnvironment() {
return environment;
},
});
const router = createBrowserRouter(routes);
return ;
}
```

`createBrowserRouter` will asynchronously run `startNavigation` that will load the data. It ends up yielding to the render of the `RouterProvider` which has a `useLayoutEffect` that will subscribe to the router to get the loaded data (which includes the preloaded queries). The first time around no data has been loaded and so the endpoint routes are not rendered. The layout effect is run and then it returns back to `startNavigation` which now provide the loaded data to each subscriber. The `RouterProvider` now rerenders with the new state and renders each route. Finally the routes call `usePreloadedQuery` and all is well.

The issue is that when the environment changes, `MyRouter` rerenders and calls `createBrowserRouter` again repeating the above process. However this time when `startNavigation` yields and `RouterProvider` renders it will have the data that was loaded last time which include the preloaded queries with the old environment. Loaded data exists and so the routes will be rendered this time. Now `usePreloadedQuery` will see that the environment from the context and the query do not match and we get the warning. `RouterProvider` will rerender like before and get a new set of preloaded queries with the updated environment and so is currently ok.

Once this becomes a hard error this is going to break pretty spectactularly and I can't think of a way for end users to avoid it except perhaps manually checking the environment and intentionally throwing and catching that in an error boundary at the root of the router and relying on it rerendering. That might work but the logs will still be full of errors since React does not provide a way to supress handled errors (https://github.com/facebook/react/issues/15069).

I hope that it will be possible to work with react-router to make this work before making this a hard error in relay.

See here for the original issue that I reported with @loop-payments/react-router-relay: https://github.com/loop-payments/react-router-relay/issues/14

Reproduction: https://github.com/steinybot/react-router-relay/blob/bug/stale-environment-2/examples/todo/src/MyRouter.tsx

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.