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

[V6] fix navigate is called synchronously in render #7203

Closed
wants to merge 2 commits into from
Closed

[V6] fix navigate is called synchronously in render #7203

wants to merge 2 commits into from

Conversation

pengx17
Copy link

@pengx17 pengx17 commented Mar 20, 2020

  • makes navigate called in effects
  • fix unit tests

The issue was found when router tries to do initial redirect navigation when rendering, but it in fact resulting in a synchronous setState somewhere. Put the call in effect fix this issue.
image

@timdorr
Copy link
Member

timdorr commented Mar 20, 2020

This will also fix #7199

@mjackson
Copy link
Member

The use of renderToStaticMarkup is intentional in the StaticRouter tests because that's how people will use a StaticRouter in their apps. They won't be using react-test-renderer, so we can't use it in our tests.

I'm not sure what the right fix for this is right now, but this PR is not it.

navigate(to, { replace, state });
React.useEffect(() => {
navigate(to, { replace, state });
}, [navigate, replace, state, to]);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this makes sense

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It breaks synchronous redirects on the initial render.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It shouldn't be synchronous, though. If there is a Redirect rendered, that navigation should happen in a second render pass. That also lets CM break up that work between frames (or not).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about useLayoutEffect? After all that's what it's able to do: synchronous side effects.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

useLayoutEffect happens after DOM manipulations, so effectively the render has already occurred. Also, those will produce errors in SSR, so you have to build detection to swap out for useEffect (which is a whole can of worms...).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

useLayoutEffect happens after DOM manipulations

I know that but it's still synchronous so that there won't be two different paints. I thought that was the original goal: to not paint a screen you'll redirect from.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry for the delay. After thinking about this a lot I think we just can't support redirect on the initial render once we get into the React lifecycle. In order to do this properly, we'd have to throw.

I'm going to change the tests around this and update our recommendation in v6 so people know if they need to redirect on the initial render, they should probably be doing it on the server.

Copy link
Contributor

@sibelius sibelius left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we should avoid side effects in render

@mjackson
Copy link
Member

we should avoid side effects in render

Normally I'd agree, except React does sometimes allow you to setState in a function component. To demonstrate, I pushed a dev-experimental-side-effect branch with a test that sets state inside the initial render. But React doesn't emit any warnings in this case. As the name of the branch suggests, this is running against React's experimental release channel.

I was trying to take advantage of that same behavior inside that initial navigate call, but it looks like React warns when a setState is triggered by some component other than the one it is currently rendering. I need to figure out if this is something we can live with or if we need to change some of our API.

@mjackson
Copy link
Member

it looks like React warns when a setState is triggered by some component other than the one it is currently rendering

Yep, that's what it's doing. I just pushed another test to show how this works. The first test doesn't emit a warning. The second one does.

mjackson added a commit that referenced this pull request Apr 3, 2020
- Removes <Redirect>
- Removes support for { redirectTo } in useRoutes()
- Warns if navigate() is used during the initial render
- Warns when <Navigate> is used on the initial render in <StaticRouter>

This began as a discussion on GitHub (see #7203). Essentially we need to
treat all navigation as a side effect, which means it needs to happen in
a useEffect. If people need v5's <Redirect> functionality (when it was
used in the route config inside a <Switch>), they either need to a) do
the redirect on the server (the best solution) or b) render a <Navigate>
on the initial render. However, (b) will only show the new route on the
subsequent render.

This should also eliminate warnings in the dev-experimental builds.

Closes #7203
Fixes #7199
@mjackson
Copy link
Member

mjackson commented Apr 4, 2020

This was fixed in cbcd398

@mjackson mjackson closed this Apr 4, 2020
@pengx17 pengx17 deleted the dev branch April 7, 2020 00:52
@lock lock bot locked as resolved and limited conversation to collaborators Jun 24, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants