Sveltekit not redirecting from unforeseen route - sveltekit

This is my code for /+layout.ts
export const ssr = false;
export const load = (async ({ url }) => {
let user: null | { user: User, player: SupaPlayer } = await supa.getUser((event, session) => {
...
});
if (user !== null) {
...
return { ...user, contestants: await supa.getContestants() };
} else {
if (!url.pathname.includes('/login')) {
// I AM REACHING THIS POINT
throw redirect(301, "/login");
}
// return null;
}
}) satisfies LayoutLoad
I am trying this with a route that does not exist (i.e. there is nothing in /routes) and get two errors:
in the terminal that I am running my code for dev I get Error: Not found: /<route> and a stacktrace - indeed the route does not exist in my code but I would expect Sveltekit not to create errors for that
in the browser, I get error: Not found: /<route> attributed to manifest.js, and, more importantly, the redirect to /login is not working, and I get Uncaught (in promise) Redirect {status: 301, location: '/login'}
I suspect the initial errors are causing the redirect to fail, as it does work for known routes.
The error seems to originate from manifest.js, which reads as below and which has no obvious default handler for an unknown route
export { matchers } from '/.svelte-kit/generated/client/matchers.js';
export const nodes = [() => import("/.svelte-kit/generated/client/nodes/0.js"),
() => import("/.svelte-kit/generated/client/nodes/1.js"),
...];
export const server_loads = [];
export const dictionary = {
"/": [2],
...
};
export const hooks = {
handleError: (({ error }) => { console.error(error) }),
};

Related

how to destructure property if possibly undefined?

I'm getting stuck on this TS error created at build time. Does anyone has any suggestions?
TypeError: Cannot destructure property 'site' of '(intermediate value)' as it is undefined.
export default function Project({
data,
preview,
}: {
data: any
preview: any
}) {
const { site, page } = data?.post
return (
<Layout site={site} page={page}>
// Stuff
</Layout>
)
}
export async function getStaticProps({ params, preview = false }) {
const { post, morePosts } = await getClient(preview).fetch(projectQuery, {
slug: params.slug,
})
return {
props: {
preview,
data: {
post,
morePosts: overlayDrafts(morePosts),
},
},
}
}
export async function getStaticPaths() {
const paths = await sanityClient.fetch(projectSlugsQuery)
return {
paths: paths.map((slug) => ({ params: { slug } })),
fallback: true,
}
}
You can't destructure it
Better to have an early return (in my opinion), and then continue as normal
if (!data) {
return null
}
const { site, page } = data.post;
// Continue on
...
data?.post will return undefined if post does not exist, so you have to add a fallback object.
const { site, page } = data?.post || {};
You can't destructure without having a source to destructure from, but you can use a default via nullish coalescing:
const { site, page } = data?.post ?? {};
// −−−−−−−−−−−−−−−−−−−−−−−−−−−−−−^^^^^^
Your current data is of type any, so that should work, but if it had a proper type you might want to provide default values for site and page. For instance if data had the type {post: {site: string; page: number; }; }:
const { site = "something", page = 42 } = data?.post ?? {};
// −−−−−−−−−^^^^^^^^^^^^^^−−−−−−^^^^^−−−−−−−−−−−−−−−^^^^^^

React-query causes the whole React tree to unmount on response error

As it is said here React docs, on uncaught errors the whole tree will unmount.
Query 1:
const { data: post } = useQuery<PostResponseDto, AxiosError>(
['fetch-post', params],
() => fetchPost(params!.postId),
{
refetchOnMount: true,
onError: (err) => {
// do something with the error
},
},
);
Query 2:
const {
data: postCommentsGroups,
fetchNextPage,
isFetchingNextPage,
hasNextPage,
refetch,
} = useInfiniteQuery<PostCommentsResponseDto, AxiosError>(
['fetch-post-comments', params],
({ pageParam = 0 }) => fetchPostComments(params!.postId, pageParam),
{
refetchOnMount: true,
getNextPageParam: (lastPage) => {
if (!lastPage.data.nextPage) {
return false;
} else {
return lastPage.data.nextPage;
}
},
onError: (err) => {
// do something with the error
},
},
);
Fetch functions:
export const fetchPost = async (webId: string) => {
const response = await axiosInstance.get(`${API_URL}/post/${webId}`);
return response.data;
};
export const fetchPostComments = async (webId: string, page: number) => {
const response = await axiosInstance.get(
`${API_URL}/post/${webId}/comments?page=${page}&limit=${COMMENTS_LIMIT}`,
);
return response.data;
};
The problem: When these queries result in status code !== 200, uncaught errors appear in the console (see below) and the whole React tree unmounts. This definitely is not the behavior I want. E.g. on both of these queries I get 404 if postId is incorrect and when this happens I want to do certain actions in onError callback (show some info to the user), but this impossible due to the uncaught errors by react-query and React unmounting the whole tree.
This is one of the few uncaught errors (AxiosErrors).
Why is this happening?
P.S.: I don't think this happens with useMutate() hook.
P.P.S: I am using global ErrorBoundary but that is irrelevant for this problem. I want to manipulate the DOM in that specific component in which queries are being made/errored.

Error during deplyment in Next.js app // getStaticPaths is failing

I am having an issue when trying to deploy my app using Vercel. I am using dynamic routes for my projects pages, therefore I am using getStaticPaths and getStaticProps in the page. The code should call an API that retrieve my records from Airtable, however, it was failing as we should not call the APIs in there, so I have replaced the API call for the API code, it is working for me in localhost, even when I use npm run build. However, when I push my code to GH, Vercel cannot build it with the following error:
message: 'there was an error retrieving the records for paths',
e: AirtableError {
error: 'NOT_FOUND',
message: 'Could not find what you are looking for',
statusCode: 404
}
}
Build error occurred
TypeError: Cannot read property 'map' of undefined
at getStaticPaths (/vercel/path0/.next/server/pages/projects/[project].js:97:29)
at processTicksAndRejections (internal/process/task_queues.js:95:5)
at async buildStaticPaths (/vercel/path0/node_modules/next/dist/build/utils.js:498:31)
at async /vercel/path0/node_modules/next/dist/build/utils.js:641:119
at async Span.traceAsyncFn (/vercel/path0/node_modules/next/dist/trace/trace.js:75:20) {
type: 'TypeError'
}
Error: Command "npm run build" exited with 1
import Image from "next/image";
import Link from "next/link";
import { projectsTable } from "../../airtable/airtable";
export const getStaticPaths = async () => {
const fetchProjects = async () => {
try {
let projects = [];
await projectsTable.select().eachPage((records, fetchNextPage) => {
records.forEach((record) => {
projects.push(record.fields);
});
fetchNextPage();
});
return projects;
} catch (e) {
return console.log({
message: "there was an error retrieving the records for paths",
e,
});
}
};
const projects = await fetchProjects();
const paths = projects.map((project) => {
return {
params: { project: project.name.toLowerCase().toString() },
};
});
return {
paths,
fallback: false,
};
};
export const getStaticProps = async (context) => {
const fetchProjects = async () => {
try {
let projects = [];
await projectsTable.select().eachPage((records, fetchNextPage) => {
records.forEach((record) => {
projects.push(record.fields);
});
fetchNextPage();
});
return projects;
} catch (e) {
return console.log({
message: "there was an error retrieving the records for props",
e,
});
}
};
const projectName = context.params.project;
const projects = await fetchProjects();
const project = projects.find(
(project) => project.name.toLowerCase().toString() == projectName
);
return {
props: { project },
};
};
The code is just fetching the array of projects and use the name to find the correct one and use it as props to generate the rest of the page content. I tried to debug it using console.log, but the projects array is correct when I try, and I dont get any other error T.T Could anyone help me with this? Anyone having same issue or maybe could spot the error in my code? Thank you a lot in advance!

React Hooks - How to test changes on global providers

I'm trying to test the following scenario:
A user with an expired token tries to access a resource he is not authorized
The resources returns a 401 error
The application updates a global state "isExpiredSession" to true
For this, I have 2 providers:
The authentication provider, with the global authentication state
The one responsible to fetch the resource
There are custom hooks for both, exposing shared logic of these components, i.e: fetchResource/expireSesssion
When the resource fetched returns a 401 status, it sets the isExpiredSession value in the authentication provider, through the sharing of a setState method.
AuthenticationContext.js
import React, { createContext, useState } from 'react';
const AuthenticationContext = createContext([{}, () => {}]);
const initialState = {
userInfo: null,
errorMessage: null,
isExpiredSession: false,
};
const AuthenticationProvider = ({ authStateTest, children }) => {
const [authState, setAuthState] = useState(initialState);
return (
<AuthenticationContext.Provider value={[authStateTest || authState, setAuthState]}>
{ children }
</AuthenticationContext.Provider>);
};
export { AuthenticationContext, AuthenticationProvider, initialState };
useAuthentication.js
import { AuthenticationContext, initialState } from './AuthenticationContext';
const useAuthentication = () => {
const [authState, setAuthState] = useContext(AuthenticationContext);
...
const expireSession = () => {
setAuthState({
...authState,
isExpiredSession: true,
});
};
...
return { expireSession };
}
ResourceContext.js is similar to the authentication, exposing a Provider
And the useResource.js has something like this:
const useResource = () => {
const [resourceState, setResourceState] = useContext(ResourceContext);
const [authState, setAuthState] = useContext(AuthenticationContext);
const { expireSession } = useAuthentication();
const getResource = () => {
const { values } = resourceState;
const { userInfo } = authState;
return MyService.fetchResource(userInfo.token)
.then((result) => {
if (result.ok) {
result.json()
.then((json) => {
setResourceState({
...resourceState,
values: json,
});
})
.catch((error) => {
setErrorMessage(`Error decoding response: ${error.message}`);
});
} else {
const errorMessage = result.status === 401 ?
'Your session is expired, please login again' :
'Error retrieving earnings';
setErrorMessage(errorMessage);
expireSession();
}
})
.catch((error) => {
setErrorMessage(error.message);
});
};
...
Then, on my tests, using react-hooks-testing-library I do the following:
it.only('Should fail to get resource with invalid session', async () => {
const wrapper = ({ children }) => (
<AuthenticationProvider authStateTest={{ userInfo: { token: 'FOOBAR' }, isExpiredSession: false }}>
<ResourceProvider>{children}</ResourceProvider>
</AuthenticationProvider>
);
const { result, waitForNextUpdate } = renderHook(() => useResource(), { wrapper });
fetch.mockResponse(JSON.stringify({}), { status: 401 });
act(() => result.current.getResource());
await waitForNextUpdate();
expect(result.current.errorMessage).toEqual('Your session is expired, please login again');
// Here is the issue, how to test the global value of the Authentication context? the line below, of course, doesn't work
expect(result.current.isExpiredSession).toBeTruthy();
});
I have tried a few solutions:
Rendering the useAuthentication on the tests as well, however, the changes made by the Resource doesn't seem to reflect on it.
Exposing the isExpiredSession variable through the Resource hook, i.e:
return {
...
isExpiredSession: authState.isExpiredSession,
...
};
I was expecting that by then this line would work:
expect(result.current.isExpiredSession).toBeTruthy();
But still not working and the value is still false
Any idea how can I implement a solution for this problem?
Author of react-hooks-testing-library here.
It's a bit hard without being able to run the code, but I think your issue might be the multiple state updates not batching correctly as they are not wrapped in an act call. The ability to act on async calls is in an alpha release of react (v16.9.0-alpha.0) and we have an issue tracking it as well.
So there may be 2 ways to solve it:
Update to the alpha version and a move the waitForNextUpdate into the act callback
npm install react#16.9.0-alpha.0
it.only('Should fail to get resource with invalid session', async () => {
const wrapper = ({ children }) => (
<AuthenticationProvider authStateTest={{ userInfo: { token: 'FOOBAR' }, isExpiredSession: false }}>
<ResourceProvider>{children}</ResourceProvider>
</AuthenticationProvider>
);
const { result, waitForNextUpdate } = renderHook(() => useResource(), { wrapper });
fetch.mockResponse(JSON.stringify({}), { status: 401 });
await act(async () => {
result.current.getResource();
await waitForNextUpdate();
});
expect(result.current.errorMessage).toEqual('Your session is expired, please login again');
expect(result.current.isExpiredSession).toBeTruthy();
});
Add in a second waitForNextUpdate call
it.only('Should fail to get resource with invalid session', async () => {
const wrapper = ({ children }) => (
<AuthenticationProvider authStateTest={{ userInfo: { token: 'FOOBAR' }, isExpiredSession: false }}>
<ResourceProvider>{children}</ResourceProvider>
</AuthenticationProvider>
);
const { result, waitForNextUpdate } = renderHook(() => useResource(), { wrapper });
fetch.mockResponse(JSON.stringify({}), { status: 401 });
act(() => result.current.getResource());
// await setErrorMessage to happen
await waitForNextUpdate();
// await setAuthState to happen
await waitForNextUpdate();
expect(result.current.errorMessage).toEqual('Your session is expired, please login again');
expect(result.current.isExpiredSession).toBeTruthy();
});
Your appetite for using alpha versions will likely dictate which option you go for, but, option 1 is the more "future proof". Option 2 may stop working one day once the alpha version hits a stable release.

Put expectaion unmet in redux-saga-test plan

So I have a saga which shows fetches some data to show in a table.
Action Creators are as follows
export const fetchInstanceDataSetAssocSuccess = (records) => {
return {
type: actionTypes.FETCH_INSTANCE_DATASETS_ASSOC_SUCCESS,
records: records
}
}
export const fetchInstanceDataSetAssocFailed = (error) => {
return {
type: actionTypes.FETCH_INSTANCE_DATASETS_ASSOC_FAILED,
error: error
}
}
export const fetchInstanceDataSetAssocStart = () => {
return {
type: actionTypes.FETCH_INSTANCE_DATASETS_ASSOC_START
}
}
export const fetchInstanceDataSetAssoc = () => {
return {
type: actionTypes.FETCH_INSTANCE_DATASETS_ASSOC_INITIATE
}
}
My saga is as follows
function * fetchInstanceDataSetAssocSaga (action) {
yield put(instanceDataSetAssocActions.fetchInstanceDataSetAssocStart())
const useMockData = yield constants.USE_MOCK_DATA
if (useMockData) {
yield delay(constants.MOCK_DELAY_SECONDS * 1000)
}
try {
const res = (useMockData)
? (yield constants.INSTANCE_DATASET_ASSOC)
: (yield call(request, {url:
API_URLS.INSTANCE_DATASET_ASSOC_API_ENDPOINT, method: 'GET'}))
yield put(instanceDataSetAssocActions.fetchInstanceDataSetAssocSuccess(res.data))
} catch (error) {
yield
put(instanceDataSetAssocActions.fetchInstanceDataSetAssocFailed(error))
}
}
Action to watch over the Saga is as follows
export function * watchInstanceDataSetAssocSaga () {
yield takeEvery(actionTypes.FETCH_INSTANCE_DATASETS_ASSOC_INITIATE,
fetchInstanceDataSetAssocSaga)
}
Test Cases are as follows
describe('load instance dataset assoc table', () => {
test('update state with instance-dataset records for landing page',() => {
const finalState = {
records: constants.INSTANCE_DATASET_ASSOC.data,
loading: false,
error: false
}
const requestParam = {url: API_URLS.INSTANCE_DATASET_ASSOC_API_ENDPOINT, method: 'GET'}
return expectSaga(watchInstanceDataSetAssocSaga)
.provide([[call(request,requestParam),constants.INSTANCE_DATASET_ASSOC]])
.withReducer(instanceDataSetAssoc)
.put(instanceDataSetAssocActions.fetchInstanceDataSetAssocStart())
.put(instanceDataSetAssocActions.fetchInstanceDataSetAssocSuccess(constants.INSTANCE_DATASET_ASSOC.data))
.dispatch(instanceDataSetAssocActions.fetchInstanceDataSetAssoc())
.hasFinalState(finalState)
.silentRun()
})
})
I get the following error for this.
SagaTestError:
put expectation unmet:
at new SagaTestError (node_modules/redux-saga-test-plan/lib/shared/SagaTestError.js:17:57)
at node_modules/redux-saga-test-plan/lib/expectSaga/expectations.js:63:13
at node_modules/redux-saga-test-plan/lib/expectSaga/index.js:572:7
at Array.forEach (<anonymous>)
at checkExpectations (node_modules/redux-saga-test-plan/lib/expectSaga/index.js:571:18)
I am following the docs correctly but still getting the above error.
Maybe its late, but i found an answer, maybe it will help you
This mistake may occure because of library timeout try to turn off the timeout with .run(false)
original link https://github.com/jfairbank/redux-saga-test-plan/issues/54

Resources