Every network request in LWS Job Portal flows through one of two Axios clients: a plainDocumentation Index
Fetch the complete documentation index at: https://mintlify.com/iDevRanjan/lws-ra-b4-assignment-five/llms.txt
Use this file to discover all available pages before exploring further.
axios instance for unauthenticated public endpoints, and a configured axiosInstance that automatically attaches a JWT Bearer token to every request. On top of those clients, TanStack React Query provides caching, background refresh, loading/error states, and infinite pagination — all driven by reusable factory functions in queryOptions.js and mutationOptions.js so the same query configuration is never written twice.
Axios Setup
The authenticated instance
src/services/axiosInstance.js creates an Axios instance with a base URL and two interceptors. The request interceptor reads the current auth token from the pub-sub store on every call — no need to restart the app or manually refresh headers after login.
The response interceptor calls
authActions.logout() on any 401 or 403. This clears the query cache, removes the token from localStorage, and resets the store atomically — ensuring a seamlessly de-authenticated UI without any component needing to handle auth errors manually.axiosInstance base URL is hardcoded to http://localhost:9000 and should be updated to match your deployment target. Public API modules (jobApi.js, authApi.js, companyApi.js) construct their full URLs from the Vite environment variable import.meta.env.VITE_API_BASE_URL and use a plain axios instance. Authenticated endpoints use axiosInstance directly with path-relative URLs (e.g., "/api/users/profile").
API Modules
jobApi.js
Public job data. Uses plain
axios for getAllJobs, getJobBySlug, getSimilarJobs (no auth needed) and axiosInstance for getRecommendedJobs (requires login).authApi.js
applicationLogin and clientRegister — both plain axios.post calls with no prior auth token.companyApi.js
Mixed: public
getCompanyBySlug / getCompanyOpenPositions via plain axios; authenticated company management endpoints via axiosInstance.userApi.js
All authenticated: profile read/update, resume upload, avatar upload, job applications. Uses
axiosInstance exclusively.jobApi.js
authApi.js
userApi.js
TanStack React Query Patterns
useQuery — single resource fetching
Single-resource queries are the most common pattern. They use queryOptions() factory functions so the same query key and fetch function work in both hooks and router loaders.
enabled flag prevents the query from firing when the user is not logged in. retry: false ensures that a failed profile fetch (e.g., a 401) is not retried — the response interceptor already handles logout on 401.
useInfiniteQuery — paginated job listings
The home page job listing and the company applicants list both use useInfiniteQuery for load-more infinite scroll. The pageParam is an integer page number; getNextPageParam returns undefined to signal the end of results.
Initial fetch
useInfiniteQuery calls queryFn with pageParam = 1 (from initialPageParam). The API returns the first page of jobs.Load More triggered
The user clicks “Load More”. React Query calls
queryFn again with pageParam = 2 (returned by getNextPageParam). New results are appended to data.pages.Filter change resets
When
params changes (new search or filter), the queryKey changes. React Query treats this as a brand-new query and restarts from initialPageParam: 1, ensuring users always see relevant results from the top.keepPreviousData (via placeholderData) means the current page list stays visible while the next page is loading — preventing a full loading flash on every filter change.
The company applicants list uses the same shape:
useMutation — creating and updating data
Mutations use mutationOptions() factory functions. Side-effects — particularly cache invalidation — live inside onSuccess callbacks so the UI stays consistent after a write.
Caching Strategy
| Setting | Value | Effect |
|---|---|---|
staleTime | 60 000 ms | Cached data is returned instantly without a background refetch for 1 minute |
gcTime | 150 000 ms | Inactive cache entries are garbage-collected after 2.5 minutes |
refetchOnMount | false | Re-visiting a page does not trigger a refetch if data is still fresh |
refetchOnReconnect | false | Coming back online does not trigger automatic refetches |
refetchOnWindowFocus | false | Switching browser tabs and back does not trigger refetches |
These conservative settings are intentional for a job portal: job listings don’t change second-to-second, and unnecessary refetches on tab-focus would waste bandwidth. Manual invalidation (
queryClient.invalidateQueries) is used instead wherever fresh data is specifically required after a write.Loading States: Skeleton Components
Every major data list has a corresponding skeleton component rendered while the queryisLoading. This gives users an immediate structural preview of the page without a blank flash.
| Skeleton component | Used during |
|---|---|
JobCardSkeleton | Home page infinite job listing |
RecentJobsCardSkeleton | Recent jobs sidebar |
ApplicationJobCardSkeleton | Job seeker applications list |
ApplicantsCardSkeleton | Company applicants list |
ManageJobsTableRowSkeleton | Company manage-jobs table |
SimilarJobSkeleton | Similar jobs panel on job detail page |
Error Handling
Errors surface through two complementary channels:- Router errorElement
- React Query isError
When a loader throws (e.g.,
getJobBySlugLoader gets a 404), React Router activates <ErrorElement> inside MainLayout — the user sees a friendly error page with the header and footer still visible. No component-level error handling is needed for loader errors.