-
Hi all, I am follow the official tutorial, but with typescript. I have problem with
The error is at So, my question is how to use the useLoaderData() with typescript correctly? |
Beta Was this translation helpful? Give feedback.
Replies: 12 comments 38 replies
-
Just stumbled across the same problem, I temporarily found the next workaround. const data = useLoaderData() as Question[]; Where I definitely know that my loader returns an array of type Questions (which is defined by me). I don't really like this work-around, as it defeats the purpose of typescript and it might introduce bugs. |
Beta Was this translation helpful? Give feedback.
-
I've solved it with a simplified version of Remix's SerializeFrom function: import { LoaderFunction } from 'react-router-dom';
export type LoaderData<TLoaderFn extends LoaderFunction> = Awaited<ReturnType<TLoaderFn>> extends Response | infer D
? D
: never; usage: your loader: export const loader = (async () => {
if (FORBIDDEN) return redirect('/signin')
return { ok: true };
}) satisfies LoaderFunction; Inside your component: const data = useLoaderData() as LoaderData<typeof loader>; It will: EDIT: Released a very thin layer library with everything-typesafe: check out react-router-typesafe |
Beta Was this translation helpful? Give feedback.
-
Specific to this tutorial (and OP's question), this is what worked for me: const { contacts } = useLoaderData() as { contacts: [] } || { contacts: [] }; The code uses the destructuring syntax on an object that looks like this:
So providing the exact type, and performing a "short-circuit evaluation" to handle null values will help resolve this. Further explanations here: |
Beta Was this translation helpful? Give feedback.
-
I remember in early Remix, you had to put those useLoaderData<typeof loader>() But it looks like the version that they back-ported to to get around it, you can do: useLoaderData() as Awaited<ReturnType<typeof loader>> But every time I put an |
Beta Was this translation helpful? Give feedback.
-
Until we have the generics for these fns, I decided to go with a helper fn that wraps the default function useDataFromLoader<LoaderFn extends LoaderFunction>(loaderFn: LoaderFn) {
return useLoaderData() as Awaited<ReturnType<typeof loaderFn>>
} Usage: async function loader() {
const data = await Promise.resolve({ foo: 'bar' })
return data
} satisfies LoaderFunction
function Component() {
const { foo } = useDataFromLoader(loader)
return <p>foo === 'bar': {foo === 'bar'}</p>
} For the export function useDataFromRouteLoader<LoaderFn extends LoaderFunction>(route: { id: string; loader: LoaderFn }) {
return useRouteLoaderData(route.id) as Awaited<ReturnType<typeof route.loader>>
} Usage: const data = useDataFromRouteLoader(routeWithLoaderAndId) I personally find this to be a simple solution to the problem, but I don't know if it's good enough to be part of the public API of the library, e.g.: have the |
Beta Was this translation helpful? Give feedback.
-
Let me give you an example: axios.get('/some/api/endpoint', {
headers: {
AUTHORIZATION: `Bearer ${authData.token}`
}
}) After some digging and with the help of this stackoverflow question I came up with the following workaround: // App.tsx
export default function App() {
const { authData } = useAuth(); // A simple hook which helps us get access to some app Context.
const apiClient = axios.create({
baseURL: 'https://domain.com',
headers: {
AUTHORIZATION: `Bearer ${authData.token}`
},
});
const router = createBrowserRouter(
createRoutesFromElements(
<Route path="content" element={<Component />} loader={contentLoader(apiClient)} />
)
);
return <RouterProvider router={router} />;
} And then in my Component: // Content.tsx
// loader as a curried function
export const loader =
(apiClient: AxiosInstance) =>
async ({ params }: LoaderFunctionArgs) => {
const contentInfo = await apiClient.get<Content>(
`content/${params.id}`
);
return contentInfo;
}; Now if we use |
Beta Was this translation helpful? Give feedback.
-
Does anyone have a solution for typing this when working with defer?? My current solution is to manually create an interface for the desired types when working with defer but would love it if we had a supported TS solution for using this hook from the maintainers if possible! |
Beta Was this translation helpful? Give feedback.
-
The lack of automated framework type safety between loaders/action and the route component is a big turn off at the moment. Could the core team address this asap? |
Beta Was this translation helpful? Give feedback.
-
This would get even worse when you use |
Beta Was this translation helpful? Give feedback.
-
Bumping this as generic support seems pretty necessary here. |
Beta Was this translation helpful? Give feedback.
-
Depending on what you think is acceptable, you could skip const Users = ({users}) => {
return (
<div>
{users.length} users
</div>
);
}
const loader = async () => {
let users = await db.select.from(users);
return <Users users={users} />
}
// You can reuse this component on every route
const Passthrough = () => useLoaderData() as JSX.Element;
const router = createBrowserRouter([
{
path: "/users",
element: <Passthrough />,
loader: loader,
},
]); |
Beta Was this translation helpful? Give feedback.
-
Imho it looks a bit weird that we don't have it in react-router-dom lib, given that remix has it. The tutorials are quite similar, the unerlying routing is (probably?) quite simlar. |
Beta Was this translation helpful? Give feedback.
Just stumbled across the same problem, I temporarily found the next workaround.
Where I definitely know that my loader returns an array of type Questions (which is defined by me).
I don't really like this work-around, as it defeats the purpose of typescript and it might introduce bugs.