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

@redwoodjs/history #2212

Merged
merged 24 commits into from Jul 15, 2021
Merged

@redwoodjs/history #2212

merged 24 commits into from Jul 15, 2021

Conversation

Tobbe
Copy link
Member

@Tobbe Tobbe commented Apr 4, 2021

The Problem

Some auth providers want to redirect the user when authorizing. They do this by a standard window.history.replaceState call (or global.history.replaceState). The address in the browser's address bar is updated, but the user isn't actually redirected.

The Cause

A standard RW app might look something like this

https://github.com/redwoodjs/redwood-tutorial/blob/3b59bc12ca211688839b395b8d1364a4c111db26/web/src/App.js#L15-L23

const App = () => (
  <FatalErrorBoundary page={FatalErrorPage}>
    <AuthProvider client={netlifyIdentity} type="netlify">
      <RedwoodApolloProvider>
        <Routes />
      </RedwoodApolloProvider>
    </AuthProvider>
  </FatalErrorBoundary>
)

Where <Routes> uses this code from the router

return (
<RouterContextProvider
useAuth={useAuth}
paramTypes={paramTypes}
pageLoadingDelay={pageLoadingDelay}
>
<LocationProvider>
<ParamsProvider>
<RouteScanner>{children}</RouteScanner>
</ParamsProvider>
</LocationProvider>
</RouterContextProvider>
)

A regular call to navigate looks like this

https://github.com/redwoodjs/redwood-tutorial/blob/2941c967adb6d7251ce3ae6e34edc3161e3fe89c/web/src/components/Post/Post.js#L36

navigate(routes.posts())

It will call this code where the address is updated (history.pushState) and all listeners are notified about the route change

navigate: (to: string) => {
global.history.pushState({}, '', to)
for (const listener of Object.values(listeners)) {
listener()
}
},

The listeners are located in <LocationProvider>. You can see that provider being used in the snippet from the router above. The provider's internal state is updated, and all its children are re-rendered. The router, being one of the children, sees the updated address and renders the new page ("posts" in this case).

This, below, is the problem

The direct call to just history.replaceState by the auth client bypasses the <LocationProvider> update, and so no re-rendering of the router happens.

The Solution

The solution might sound obvious. Just call the location listeners, like with a regular navigate() call.

We can't do this however, because the auth stuff is higher up in the hierarchy and so can't use the location context with its listeners.

My proposal

I propose that we introduce a new package, "history", that's responsible for keeping track of history, history changes and history change notification. This would be placed above auth in the hierarchy so auth can update the history (by calling history.replace), and the LocationProvider can keep listening to changes to history, and re-render its children when it changes.

Closes #1892


Adding some more context from #1892

This is what a typical App.js file looks like with auth0 added

const App = () => (
  <FatalErrorBoundary page={FatalErrorPage}>
    <AuthProvider client={auth0} type="auth0">
      <RedwoodApolloProvider>
        <Routes />
      </RedwoodApolloProvider>
    </AuthProvider>
  </FatalErrorBoundary>
)

When signing up you do something like this

<button
  onClick={() =>
    signUp({
      appState: { targetUrl: '/after-auth' },
    })
  }
>
  Sign In
</button>

When <AuthProvider> from the App snippet above is mounted it calls restoreAuthState, which in turn does this

restoreAuthState: async () => {
  if (
    global?.location?.search?.includes('code=') &&
    global?.location?.search?.includes('state=')
  ) {
    const { appState } = await client.handleRedirectCallback()
    history.replace(
      {},
      document.title,
      appState && appState.targetUrl
        ? appState.targetUrl
        : window.location.pathname
    )
  }
},

The problem is with the history.replace call. The router doesn't know about this (doesn't get any notifications). So the correct page isn't rendered. In fact, no rerender happens at all. So the user just stays on the current page. But the browser knows, so the URL is updated.


Here's a Github repo with a project that displays the issue https://github.com/tobbe/tobbe-redwood-history
Using the code in this PR the issue goes away. There's some info in the README in the repo on how to set it up.

@github-actions
Copy link

github-actions bot commented Apr 4, 2021

📦 PR Packages

Click to Show Package Download Links

https://rw-pr-redwoodjs-com.s3.amazonaws.com/2212/create-redwood-app-0.28.4-f207919.tgz
https://rw-pr-redwoodjs-com.s3.amazonaws.com/2212/redwoodjs-api-0.28.4-f207919.tgz
https://rw-pr-redwoodjs-com.s3.amazonaws.com/2212/redwoodjs-api-server-0.28.4-f207919.tgz
https://rw-pr-redwoodjs-com.s3.amazonaws.com/2212/redwoodjs-auth-0.28.4-f207919.tgz
https://rw-pr-redwoodjs-com.s3.amazonaws.com/2212/redwoodjs-cli-0.28.4-f207919.tgz
https://rw-pr-redwoodjs-com.s3.amazonaws.com/2212/redwoodjs-core-0.28.4-f207919.tgz
https://rw-pr-redwoodjs-com.s3.amazonaws.com/2212/redwoodjs-dev-server-0.28.4-f207919.tgz
https://rw-pr-redwoodjs-com.s3.amazonaws.com/2212/redwoodjs-eslint-config-0.28.4-f207919.tgz
https://rw-pr-redwoodjs-com.s3.amazonaws.com/2212/redwoodjs-eslint-plugin-redwood-0.28.4-f207919.tgz
https://rw-pr-redwoodjs-com.s3.amazonaws.com/2212/redwoodjs-forms-0.28.4-f207919.tgz
https://rw-pr-redwoodjs-com.s3.amazonaws.com/2212/redwoodjs-history-0.28.4-f207919.tgz
https://rw-pr-redwoodjs-com.s3.amazonaws.com/2212/redwoodjs-internal-0.28.4-f207919.tgz
https://rw-pr-redwoodjs-com.s3.amazonaws.com/2212/redwoodjs-prerender-0.28.4-f207919.tgz
https://rw-pr-redwoodjs-com.s3.amazonaws.com/2212/redwoodjs-router-0.28.4-f207919.tgz
https://rw-pr-redwoodjs-com.s3.amazonaws.com/2212/redwoodjs-structure-0.28.4-f207919.tgz
https://rw-pr-redwoodjs-com.s3.amazonaws.com/2212/redwoodjs-testing-0.28.4-f207919.tgz
https://rw-pr-redwoodjs-com.s3.amazonaws.com/2212/redwoodjs-web-0.28.4-f207919.tgz

Install this PR by running yarn rw upgrade --pr 2212:0.28.4-f207919

@thedavidprice thedavidprice marked this pull request as draft April 6, 2021 22:40
@KrisCoulson
Copy link
Contributor

@Tobbe love this! I was thinking about picking up this smaller ticket but it might be worth lumping in with your new package. History back functionality: #1013

@Tobbe
Copy link
Member Author

Tobbe commented Apr 14, 2021

@KrisCoulson I'm not going to have time to look at this for a while. Do you want to pick it up instead?

@jtoar jtoar added this to hopper in Triage Jun 9, 2021
@jtoar jtoar removed this from hopper in Triage Jun 10, 2021
@jtoar jtoar added this to Backlog PRs in Current-Release-Sprint via automation Jun 10, 2021
@jtoar jtoar added v1/priority and removed hopper labels Jun 10, 2021
@jtoar jtoar moved this from Backlog PRs to On deck (future-release; help-wanted) in Current-Release-Sprint Jun 10, 2021
@Tobbe Tobbe force-pushed the tobbe-redwood-history branch 4 times, most recently from 0eba0b0 to 2ced364 Compare June 14, 2021 23:25
@sangheestyle
Copy link

sangheestyle commented Jun 15, 2021

Hi @Tobbe ,

Can we make a separate PR for the jump first? I use global.history.replaceState() when I need it but it might be good if rw router have one for it. So the listeners can be used seamlessly.

jump: (to: string, title: string = '') => {

@Tobbe Tobbe force-pushed the tobbe-redwood-history branch 6 times, most recently from 93d3af0 to 0b6612d Compare June 15, 2021 09:01
@Tobbe
Copy link
Member Author

Tobbe commented Jun 15, 2021

Hi @Tobbe ,

Can we make a separate PR for the jump first? I use global.history.replaceState() when I need it but it might be good if rw router have one for it. So the listeners can be used seamlessly.

Hi @sangheestyle thanks for showing interest in this PR 🙂 I will spend some more time on this, and then discuss with the rest of the team about the best way forward.

@sangheestyle
Copy link

Hi @Tobbe ,
Can we make a separate PR for the jump first? I use global.history.replaceState() when I need it but it might be good if rw router have one for it. So the listeners can be used seamlessly.

Hi @sangheestyle thanks for showing interest in this PR 🙂 I will spend some more time on this, and then discuss with the rest of the team about the best way forward.

Awesome! Thanks for the reply!

@Tobbe
Copy link
Member Author

Tobbe commented Jun 15, 2021

@sangheestyle We decided to focus on getting this PR done as soon as possible instead of spending time on a separate PR for jump(). I don't want to make any promises, but we'll try to get this done this or next week.

@thedavidprice thedavidprice moved this from On deck (help-wanted) to In progress (priority) in Current-Release-Sprint Jun 15, 2021
@jtoar jtoar merged commit 555b98c into redwoodjs:main Jul 15, 2021
Current-Release-Sprint automation moved this from Ready to merge to Done Jul 15, 2021
@thedavidprice
Copy link
Contributor

@jtoar confirming there are no breaking changes from this PR, correct? Also, is there anything we should do for documentation? Assuming "no".

@jtoar
Copy link
Contributor

jtoar commented Jul 20, 2021

@thedavidprice no breaking changes. But ideally this PR will be superseded by #3055 before our next release (also no breaking changes).

We could add to the router documentation for the new replace option to navigate and the back function.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
No open projects
Development

Successfully merging this pull request may close these issues.

RestoreAuthState no longer functioning correctly
5 participants