remix: Unexpected behaviour when redirecting in action
What version of Remix are you using?
1.4.1
Steps to Reproduce
Create a Remix project (with the built in Remix server) with the following routes:
index.tsx:
import { memo } from 'react'
const IndexRoute = memo(() => {
return <h1>Home</h1>
})
IndexRoute.displayName = 'IndexRoute'
export default IndexRoute
magic-link.tsx:
import { memo, useEffect } from 'react'
import { LoaderFunction, json, useLoaderData, useSubmit } from 'remix'
import { emailFormFieldName, codeFormFieldName } from '~/routes/verify-magic-link'
type LoaderData = {
email: string,
code: string
}
const loader: LoaderFunction = async ({ request }) => {
const url = new URL(request.url)
const email = url.searchParams.get("email") ?? ''
const code = url.searchParams.get("code") ?? ''
return json<LoaderData>({
email,
code
})
}
const MagicLinkRoute = memo(() => {
const loaderData = useLoaderData<LoaderData>()
const submit = useSubmit()
useEffect(() => {
const formData = new FormData()
formData.append(emailFormFieldName, loaderData.email)
formData.append(codeFormFieldName, loaderData.code)
submit(formData, { method: 'post', action: '/verify-magic-link' })
}, [loaderData.email, loaderData.code, submit])
return (
<p>Some pending UI</p>
)
})
MagicLinkRoute.displayName = 'MagicLinkRoute'
export default MagicLinkRoute
export { loader }
verify-magic-link.tsx:
import { memo } from 'react'
import { ActionFunction, json, redirect, useActionData } from 'remix'
export const emailFormFieldName = 'email'
export const codeFormFieldName = 'code'
// fake verifyMagicLink function with forced failure
const verifyMagicLink = ({ email, code } : { email: string, code: string }) => Promise.resolve(false)
type ActionData = Awaited<ReturnType<typeof verifyMagicLink>>
const action: ActionFunction = async ({ request }) => {
const body = await request.formData()
const email = body.get(emailFormFieldName)?.toString() ?? ''
const code = body.get(codeFormFieldName)?.toString() ?? ''
const verifyMagicLinkResult = await verifyMagicLink({ email, code })
if (verifyMagicLinkResult === true) {
return redirect('/')
}
return json<ActionData>(verifyMagicLinkResult)
}
const VerifyMagicLinkRoute = memo(() => {
const actionData = useActionData<ActionData>()
return (
<p>{JSON.stringify(actionData, null, 2)}</p>
)
})
VerifyMagicLinkRoute.displayName = 'VerifyMagicLinkRoute'
export default VerifyMagicLinkRoute
export { action }
In the entry.server.tsx handleRequest function put the following just before returning the response:
console.log({
type: 'handleRequest',
url: request.url,
method: request.method,
response: newResponse.status
})
In the entry.server.tsx handleDataRequest function put the following just before returning the response:
console.log({
type: 'handleDataRequest',
url: request.url,
method: request.method,
response: newResponse.status
})
Go to https://localhost:3000/magic-link?code=123456&email=test@test.com in your browser. In the terminal you should see:
{
type: 'handleRequest',
url: 'http://localhost:3000/magic-link?code=123456&email=test@test.com',
method: 'GET',
response: 200
}
{
type: 'handleDataRequest',
url: 'http://localhost:3000/verify-magic-link?_data=routes%2Fverify-magic-link',
method: 'POST',
response: 200
}
{
type: 'handleDataRequest',
url: 'http://localhost:3000/verify-magic-link?_data=root',
method: 'GET',
response: 200
}
This is what I would expect to see.
Now in verify-magic-link.tsx change const verifyMagicLink = ({ email, code } : { email: string, code: string }) => Promise.resolve(false) to const verifyMagicLink = ({ email, code } : { email: string, code: string }) => Promise.resolve(true).
Run the same request in your browser again.
Expected Behavior
Terminal should show:
{
type: 'handleRequest',
url: 'http://localhost:3000/magic-link?code=123456&email=test@test.com',
method: 'GET',
response: 200
}
{
type: 'handleDataRequest',
url: 'http://localhost:3000/verify-magic-link?_data=routes%2Fverify-magic-link',
method: 'POST',
response: 302
}
{
type: 'handleDataRequest',
url: 'http://localhost:3000/?_data=root',
method: 'GET',
response: 200
}
Actual Behavior
Terminal shows:
{
type: 'handleRequest',
url: 'http://localhost:3000/magic-link?code=123456&email=test@test.com',
method: 'GET',
response: 200
}
{
type: 'handleDataRequest',
url: 'http://localhost:3000/?_data=root',
method: 'GET',
response: 200
}
The POST request does not get logged by either handleRequest or handleDataRequest.
Even though I am taken to the index.tsx route as expected the 302 redirect response never seems to be returned to the browser. Instead when I look in the browser network tab I can see that the the response to the POST request was a 204 response.
This is causing me issues because I would like to add a set-cookie header on the redirect response but the cookie never gets set because the redirect response doesn’t make it to the browser.
About this issue
- Original URL
- State: closed
- Created 2 years ago
- Reactions: 2
- Comments: 23 (2 by maintainers)
Commits related to this issue
- use custom livereload from 1.6.4 of remix see https://github.com/remix-run/remix/issues/2997#issuecomment-1233379949 — committed to Virtual-Coffee/virtualcoffee.io by danieltott 2 years ago
- Feature: auth (#635) * Get basic auth running with errors * add login method * logging * add register endpoint * stop using forked version of form * testing error states * fix dev s... — committed to Virtual-Coffee/virtualcoffee.io by danieltott 2 years ago
This issue has been automatically closed because we haven’t received a response from the original author 🙈. This automation helps keep the issue tracker clean from issues that are unactionable. Please reach out if you have more information for us! 🙂
@fknop I had the same issue - lots of weird things happening with
LiveReloadandremix-auth, especially re: redirecting when trying to log in. I verified that using the old version ofLiveReloadstops this problem from happening. Here’s the “custom”LiveReloadfunction I’m using instead of the production one from remix (which is just the1.6.4version with no other changes):I believe PR 4725 fixes this issue.
As mentioned before this was introduced in #859 to enable automatic re-connection when the dev server is restarted. Would it be possible to check for the close event code before setting the timeout? This would keep the auto reconnect behavior while fixing the Firefox behavior (at least in a quick test):
LiveReloadcomponent codeThe old version of LiveReload solves my issues as well. I believe it breaks due to this added block:
More specifically, I think the arbitrary 1000 ms timeout before reloading creates a race condition, where, during a redirect, the route you’re navigating from will reload itself before the redirect has been able to complete. By increasing the timeout, the issues disappeared for my part, but that does not seem like a viable permanent fix.
I am new to Remix. Just started last week.
I am using latest indie stack.
After I build the app for production, and run it in production mode using
cross-env NODE_ENV=production remix-serve buildSafari: After login it will not redirect to the Notes page.
Chrome: After login redirecting to Notes page just fine.
My observation: Safari seems to be not redirecting due to secure: true turned on in the createCookieSessionStorage setting. When I forced secure to
falseit works fine. That explains why this is working okay innpm run devand notnpm startLet me know what I can do to improve?
@machour can you reopen this issue please?
I’ve also done a quick test with @danieljb’s
event.codefix above and it appears to fix the issue in Firefox.I confirmed that
<LiveReload />is causing problems when dev with redirecting in Firefox (e.g. using remix-auth). It doesn’t seem to cause any problems in Chrome or Safari.I seem to have the same issue with the
remix-authlibrary, the redirect to the Github OAuth flow seems to not happen properly and I have a 204. It used to work with remix1.4.3Update: It actually still works in 1.6.4, but starts breaking in 1.6.5
Update 2:
I think I managed to pinpoint my own issue, I don’t know if this is related to the original issue of this thread, but remix-auth-github properly redirects when
<LiveReload />is commented. The only change I see from 1.6.5 is this PR: https://github.com/remix-run/remix/pull/859When a loader/action returns a redirect from a client-side navigation, Remix uses status 204 and a header
X-Remix-Redirect. This is so that the actual fetch call doesn’t try to redirect, but instead Remix will handle the redirect. Also, if the response includes aset-cookieheader, it will addX-Remix-Revalidateto ensure that all loaders are revalidated.https://github.com/remix-run/remix/blob/a2e1449760f788cc424421e53eb98ae82d4f64d1/packages/remix-server-runtime/server.ts#L135-L149