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

[Bug]: Type of loaderData wrong when returning redirect in the loader (action as well) #12335

Closed
mrNuTz opened this issue Nov 21, 2024 · 13 comments · Fixed by #12527
Closed

[Bug]: Type of loaderData wrong when returning redirect in the loader (action as well) #12335

mrNuTz opened this issue Nov 21, 2024 · 13 comments · Fixed by #12527
Assignees
Labels
awaiting release This issue have been fixed and will be released soon bug

Comments

@mrNuTz
Copy link

mrNuTz commented Nov 21, 2024

What version of React Router are you using?

7

Steps to Reproduce

export const loader = () => {
  if (Math.random() < 0.5) {
    return redirect('/')
  }
  return {some: 'data'}
}

export default function Home() {
  const data = userLoaderData<typeof loader>()
  return <>{data.some}</> // ERROR: some not in type {headers: ...}
}

Expected Behavior

some should be defined on the loaderData

Actual Behavior

there is a typescript error

@mrNuTz mrNuTz added the bug label Nov 21, 2024
@Moonflower-labs
Copy link

Hi, I had a similar issue. If you throw the redirect instead of returning it, the typescript error should go away.

@goamaan
Copy link

goamaan commented Nov 22, 2024

Facing the same problem - just migrated from remix to rr7 and the inferred return types for actions/loaders are messed up. I'm returning a redirect / createUserSession from my actions and this used to work but now shows a type error when trying to access anything on fetcher.data (when using const fetcher = useFetcher<typeof action>() in RR7

@brookslybrand
Copy link
Contributor

Since redirect returns a response, you'll want to throw it to get the proper types, otherwise the inference thinks you're returning an object

@goamaan
Copy link

goamaan commented Nov 22, 2024

Confused as to why this did work in Remix though?

@brookslybrand
Copy link
Contributor

Confused as to why this did work in Remix though?

Good point, not sure if there was something we should be carrying over from Remix

c.c. @pcattori

@goamaan
Copy link

goamaan commented Nov 22, 2024

Also, if I need to redirect the user after logging in, I do something like this in my action

return createUserSession({
            request,
            userId: user.id,
            email,
            roles,
            remember: true,
            redirectTo: redirectTo ?? "/app",
            isAdmin: user.isAdmin,
        })

where createUserSession is this

async function createUserSession({
    request,
    userId,
    remember,
    redirectTo,
    roles,
    email,
    isAdmin,
}: {
    ...
}) {
    const { session } = await getSession(request)
    ...
    session.set(IS_ADMIN, isAdmin)
    return redirect(redirectTo, {
        headers: {
            "Set-Cookie": await commitSession(session, {
                maxAge: remember
                    ? 60 * 60 * 24 * 2 // 2 days
                    : undefined,
            }),
        },
    })
}

in this case throwing doesn't seem work Nvm it does work, I just have to throw from createUserSession instead of from the action

@pveyes
Copy link

pveyes commented Nov 24, 2024

The fix is to exclude Response type from loader and action. I've created a draft PR, but not sure what tests to add (and how) considering it's a type fix

@Varkoff
Copy link

Varkoff commented Dec 5, 2024

Also, if I need to redirect the user after logging in, I do something like this in my action

return createUserSession({
            request,
            userId: user.id,
            email,
            roles,
            remember: true,
            redirectTo: redirectTo ?? "/app",
            isAdmin: user.isAdmin,
        })

where createUserSession is this

async function createUserSession({
    request,
    userId,
    remember,
    redirectTo,
    roles,
    email,
    isAdmin,
}: {
    ...
}) {
    const { session } = await getSession(request)
    ...
    session.set(IS_ADMIN, isAdmin)
    return redirect(redirectTo, {
        headers: {
            "Set-Cookie": await commitSession(session, {
                maxAge: remember
                    ? 60 * 60 * 24 * 2 // 2 days
                    : undefined,
            }),
        },
    })
}

in this case throwing doesn't seem work Nvm it does work, I just have to throw from createUserSession instead of from the action

I had the same issue. In Remix 2.x, returning a redirect somehow worked and didn't mess with the loader / action types.

Having to " throw " a successful authentication request seems a bit weird to me. And it introduces a strange behavior in my app, I need to fully reload before showing the authenticated user info.

@sdifru
Copy link

sdifru commented Dec 9, 2024

Is it the recommended practice to throw redirect? I read the docs and it shows that it returns redirect, not throwing it.

@GrahamQuan
Copy link

GrahamQuan commented Dec 10, 2024

same here, i end up with undefined | dataType like

import { redirect } from 'react-router';
import type { Route } from './+types/blog';

type BlogType = {
  userId: number;
  id: number;
  title: string;
  body: string;
};

export async function loader({ params }: Route.LoaderArgs) {
  const data = await fetch(
    `https://jsonplaceholder.typicode.com/posts/${params.id}`
  );
  if (!data) {
    redirect('/');
    return; // return undefined as default
  }

  return (await data.json()) as BlogType;
}

export default function Blog({
  params,
  loaderData,
}: Route.LoaderArgs & Route.ComponentProps) {
  // check if loaderData is undefined
  if (!loaderData) {
    return null;
  }

  return (
    <div>
      <h1>blog id: {params?.id}</h1>
      <p>{loaderData.title}</p>
      <p>{loaderData.body}</p>
    </div>
  );
}

edit:
looks like they throw it instead of return
#12506

@MattyBalaam
Copy link

MattyBalaam commented Dec 10, 2024

If you throw a redirect instead of returning in an action or loader when using createRoutesStub this causes the tests to show an error boundary.

The workaround I have found is to assert the return as never

return redirect('/horrible-workaround') as never

Copy link
Contributor

🤖 Hello there,

We just published version 7.1.0-pre.0 which involves this issue. If you'd like to take it for a test run please try it out and let us know what you think!

Thanks!

@brophdawg11 brophdawg11 added the awaiting release This issue have been fixed and will be released soon label Dec 18, 2024
Copy link
Contributor

🤖 Hello there,

We just published version 7.1.0 which involves this issue. If you'd like to take it for a test run please try it out and let us know what you think!

Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
awaiting release This issue have been fixed and will be released soon bug
Projects
None yet
Development

Successfully merging a pull request may close this issue.