0% found this document useful (0 votes)
1 views12 pages

MERN Reference Code

The document provides a comprehensive overview of the frontend layer of a MERN application, detailing the implementation of an Axios API layer with automatic token refresh, an authentication context, and protected routes. It includes reusable hooks and components for data fetching and form handling, as well as a Socket.io client for real-time updates. Key architectural decisions are highlighted, such as using Context API for state management and the handling of token refresh race conditions.

Uploaded by

Devil
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
1 views12 pages

MERN Reference Code

The document provides a comprehensive overview of the frontend layer of a MERN application, detailing the implementation of an Axios API layer with automatic token refresh, an authentication context, and protected routes. It includes reusable hooks and components for data fetching and form handling, as well as a Socket.io client for real-time updates. Key architectural decisions are highlighted, such as using Context API for state management and the handling of token refresh race conditions.

Uploaded by

Devil
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd

MERN Reference Code — Frontend Layer

The backend doc already covers Express/Mongoose/JWT/[Link]. This fills in the React side
— the pieces that talk to that backend: an auth context, a protected-route wrapper, an Axios
API layer with token refresh, a role-based dashboard, and a couple of reusable
hooks/components.

1. Axios API layer with automatic token refresh

// api/[Link]

import axios from 'axios';

const api = [Link]({

baseURL: [Link].REACT_APP_API_URL,

withCredentials: true, // sends the httpOnly refresh-token cookie

});

// attach access token to every request

[Link]((config) => {

const token = [Link]('accessToken');

if (token) [Link] = `Bearer ${token}`;

return config;

});

// if a request fails with 401, try refreshing the access token once, then retry

let isRefreshing = false;

let queue = [];

[Link](

(res) => res,

async (error) => {


const originalRequest = [Link];

if ([Link]?.status === 401 && !originalRequest._retry) {

if (isRefreshing) {

// queue requests that arrive while a refresh is already in flight

return new Promise((resolve, reject) => {

[Link]({ resolve, reject });

}).then((token) => {

[Link] = `Bearer ${token}`;

return api(originalRequest);

});

originalRequest._retry = true;

isRefreshing = true;

try {

const { data } = await [Link](

`${[Link].REACT_APP_API_URL}/auth/refresh`,

{},

{ withCredentials: true }

);

[Link]('accessToken', [Link]);

[Link](({ resolve }) => resolve([Link]));

queue = [];

[Link] = `Bearer ${[Link]}`;

return api(originalRequest);

} catch (refreshError) {
[Link](({ reject }) => reject(refreshError));

queue = [];

[Link]('accessToken');

[Link] = '/login';

return [Link](refreshError);

} finally {

isRefreshing = false;

return [Link](error);

);

export default api;

Why the queue? If five API calls fire at once and all get a 401, you don't want five separate
refresh requests racing each other — the first one refreshes, and the other four wait on the
queue and retry with the new token once it resolves.

2. Auth context — global user/role state

// context/[Link]

import { createContext, useContext, useState, useEffect } from 'react';

import api from '../api/client';

const AuthContext = createContext(null);

export function AuthProvider({ children }) {

const [user, setUser] = useState(null);

const [loading, setLoading] = useState(true);


useEffect(() => {

// on app load, try to restore session from the refresh cookie

[Link]('/auth/refresh')

.then(({ data }) => {

[Link]('accessToken', [Link]);

return [Link]('/auth/me');

})

.then(({ data }) => setUser(data))

.catch(() => setUser(null))

.finally(() => setLoading(false));

}, []);

async function login(email, password) {

const { data } = await [Link]('/auth/login', { email, password });

[Link]('accessToken', [Link]);

setUser({ role: [Link], email });

function logout() {

[Link]('accessToken');

setUser(null);

[Link]('/auth/logout');

return (

<[Link] value={{ user, loading, login, logout }}>

{children}
</[Link]>

);

export const useAuth = () => useContext(AuthContext);

3. Protected + role-gated routes

// routes/[Link]

import { Navigate, Outlet } from 'react-router-dom';

import { useAuth } from '../context/AuthContext';

export function ProtectedRoute() {

const { user, loading } = useAuth();

if (loading) return <Spinner />;

return user ? <Outlet /> : <Navigate to="/login" replace />;

export function RoleRoute({ allowedRoles }) {

const { user, loading } = useAuth();

if (loading) return <Spinner />;

if (!user) return <Navigate to="/login" replace />;

if (![Link]([Link])) return <Navigate to="/unauthorized" replace />;

return <Outlet />;

// [Link]

import { BrowserRouter, Routes, Route } from 'react-router-dom';

import { ProtectedRoute, RoleRoute } from './routes/ProtectedRoute';


function App() {

return (

<BrowserRouter>

<Routes>

<Route path="/login" element={<Login />} />

<Route element={<ProtectedRoute />}>

<Route path="/dashboard" element={<Dashboard />} />

<Route element={<RoleRoute allowedRoles={['admin', 'committee']} />}>

<Route path="/admin" element={<AdminPanel />} />

</Route>

<Route element={<RoleRoute allowedRoles={['maintenance']} />}>

<Route path="/maintenance" element={<MaintenanceQueue />} />

</Route>

</Route>

</Routes>

</BrowserRouter>

);

Nesting RoleRoute inside ProtectedRoute mirrors the backend's two-layer check


(authenticate, then authorize) — a clean thing to point out if an interviewer asks how
frontend and backend auth logic relate.

4. Reusable data-fetching hook

// hooks/[Link]

import { useState, useEffect, useCallback } from 'react';

import api from '../api/client';


export function useFetch(url, deps = []) {

const [data, setData] = useState(null);

const [error, setError] = useState(null);

const [loading, setLoading] = useState(true);

const fetchData = useCallback(async () => {

setLoading(true);

try {

const res = await [Link](url);

setData([Link]);

setError(null);

} catch (err) {

setError(err);

} finally {

setLoading(false);

}, [url]);

useEffect(() => {

fetchData();

}, [...deps, fetchData]);

return { data, error, loading, refetch: fetchData };

// Usage

function ComplaintsList() {

const { data: complaints, loading, error, refetch } = useFetch('/complaints');


if (loading) return <Spinner />;

if (error) return <ErrorBanner message="Couldn't load complaints" />;

return (

<ul>

{[Link]((c) => (

<ComplaintCard key={c._id} complaint={c} onUpdate={refetch} />

))}

</ul>

);

5. [Link] client — matching the backend's room pattern

// hooks/[Link]

import { useEffect, useRef } from 'react';

import { io } from '[Link]-client';

export function useSocket(onEvents = {}) {

const socketRef = useRef(null);

useEffect(() => {

const token = [Link]('accessToken');

[Link] = io([Link].REACT_APP_SOCKET_URL, {

auth: { token },

});

[Link](onEvents).forEach(([event, handler]) => {


[Link](event, handler);

});

return () => [Link]();

// eslint-disable-next-line react-hooks/exhaustive-deps

}, []);

return socketRef;

// Usage — live-update a complaint's status without polling

function ComplaintDetail({ complaintId }) {

const [status, setStatus] = useState(null);

useSocket({

'complaint:updated': (payload) => {

if ([Link] === complaintId) setStatus([Link]);

},

});

return <StatusBadge status={status} />;

6. Reusable form component (the "component library" line on your resume)

// components/[Link]

export function FormField({ label, error, children }) {

return (

<div className="form-field">

<label>{label}</label>
{children}

{error && <span className="form-error">{error}</span>}

</div>

);

// components/[Link]

export function Button({ variant = 'primary', loading, children, ...props }) {

return (

<button className={`btn btn-${variant}`} disabled={loading} {...props}>

{loading ? <Spinner size="sm" /> : children}

</button>

);

// Usage — a complaint submission form built from the shared pieces

function NewComplaintForm({ onSuccess }) {

const [form, setForm] = useState({ category: 'noise', description: '' });

const [submitting, setSubmitting] = useState(false);

const [error, setError] = useState('');

async function handleSubmit(e) {

[Link]();

setSubmitting(true);

try {

await [Link]('/complaints', form);

onSuccess();

} catch (err) {

setError([Link]?.data?.message || 'Something went wrong');


} finally {

setSubmitting(false);

return (

<form onSubmit={handleSubmit}>

<FormField label="Category">

<select

value={[Link]}

onChange={(e) => setForm({ ...form, category: [Link] })}

>

<option value="noise">Noise</option>

<option value="maintenance">Maintenance</option>

<option value="security">Security</option>

</select>

</FormField>

<FormField label="Description" error={error}>

<textarea

value={[Link]}

onChange={(e) => setForm({ ...form, description: [Link] })}

/>

</FormField>

<Button type="submit" loading={submitting}>Submit Complaint</Button>

</form>

);
}

Quick talking points if asked about the frontend architecture

• State management: Context API for auth (global, low-frequency updates) rather than
Redux — a reasonable call for an app this size; if pushed on "why not Redux," the
honest answer is auth state doesn't need the ceremony of actions/reducers when a
single context covers it.

• Token refresh race condition: the isRefreshing + queue pattern in the Axios
interceptor is the single most interview-worthy piece of frontend code here — be
ready to explain it without the code in front of you.

• Route nesting mirrors backend middleware order: ProtectedRoute (authentication)


wraps RoleRoute (authorization), same two-step check as the backend's authenticate
→ authorize middleware chain.

You might also like