Skip to main content

Lesson 7: Server Submission

🏳️ Haven't completed the previous lesson?

No worries! You can pickup from here:

git checkout tags/lesson-7

In this lesson, we'll enhance the submission process to handle both offline storage and online server submission. We'll set up a JSON server to simulate a backend API and modify our Review page to automatically submit data to the server when online, while gracefully falling back to local storage when offline.

Getting Started

Before we begin, make sure you have completed the previous lessons and have the development server running. We'll be working primarily with the Review page submission logic and setting up a mock backend server.

Step 1: Setting Up the JSON Server

We'll use json-server to create a mock REST API that simulates a real backend server for testing online submission.

1.1: Understanding the Server Setup

The project already includes the necessary configuration for running a JSON server:

json-server has already been installed as a development dependency. Here is the command for your reference:

npm install --save-dev json-server

Package.json script:

package.json
{
"scripts": {
"server": "json-server --watch db.json --port 3002"
}
}

Database file:

db.json
{
"trips": []
}

1.2: Start the JSON Server

Open a new terminal window (keep your development server running in the original terminal) and run:

npm run server

You should see output similar to:

JSON Server started on PORT :3002
Press CTRL-C to stop
Watching db.json...

Index:
http://localhost:3002/

Static files:
Serving ./public directory if it exists

Endpoints:
http://localhost:3002/trips

Understanding the Setup:

  • Port 3002: The server runs on a different port than your React app (3000) to avoid conflicts
  • Watch mode: The server automatically reloads when db.json changes
  • REST endpoints: Automatically creates GET, POST, PUT, DELETE endpoints for the trips resource
Dev Tip: Proxy Configuration

The React app can communicate with the JSON server through a proxy configuration in vite.config.js:

vite.config.js
server: {
open: true,
port: 3000,
proxy: {
"/api": {
target: "http://localhost:3002",
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, ""),
},
},
},

How the Proxy Works:

  • Request: When your React app makes a request to /api/trips
  • Proxy intercepts: Vite development server catches requests starting with /api
  • Rewrites URL: Removes /api prefix, so /api/trips becomes /trips
  • Forwards: Sends the request to http://localhost:3002/trips (JSON server)
  • Response: JSON server response is sent back to your React app

This allows your frontend to use /api/trips in fetch calls while the actual JSON server runs on a different port without CORS issues.

Step 2: Understanding Server Submission Logic

The Review page needs to detect whether the user is online or offline and handle submission accordingly.

2.1: Current Submission Logic

Let's examine the current submission logic in the Review page:

src/pages/ReviewSubmit.jsx
const handleSubmit = async () => {
if (!trip) return;

const tripStore = app.stores[STORE_NAMES.TRIP_STORE];
const tripCollection = tripStore.getCollection(COLLECTION_NAMES.TRIP_COLLECTION);

try {
const finalStatus = isOffline ? "Not Submitted" : "submitted";

await tripCollection.update({
id: trip.id,
status: finalStatus,
step: 4,
});

navigate(isOffline ? "/offline-confirm" : "/online-confirm");
} catch (error) {
console.error("Error submitting trip:", error);
setError("Failed to submit trip. Please try again.");
}
};

Current Behavior:

  • Offline: Sets status to "Not Submitted" and saves locally
  • Online: Sets status to "submitted" but only saves locally
  • Missing: Actual server submission when online

2.2: Enhanced Submission Strategy

We need to modify this logic to actually submit to the server when online:

  1. Check online status using useOfflineStatus
  2. If offline: Save locally with "Not Submitted" status
  3. If online: Submit to server AND save locally with "submitted" status
  4. Handle errors: Fall back to offline mode if server submission fails

Step 3: Implementing Server Submission

We'll break the server submission implementation into smaller, manageable steps to make it easier to follow along.

3.1: Add Basic Error Handling Structure

First, let's replace the simple submission logic with a structure that handles offline and online cases separately. We'll add try/catch blocks for proper error handling:

src/pages/ReviewSubmit.jsx
const handleSubmit = async () => {
if (!trip) return;

const tripStore = app.stores[STORE_NAMES.TRIP_STORE];
const tripCollection = tripStore.getCollection(COLLECTION_NAMES.TRIP_COLLECTION);

try {
const finalStatus = isOffline ? "Not Submitted" : "submitted";

await tripCollection.update({
id: trip.id,
status: finalStatus,
step: 4,
});

navigate(isOffline ? "/offline-confirm" : "/online-confirm");
} catch (error) {
console.error("Error submitting trip:", error);
setError("Failed to submit trip. Please try again.");
}
if (isOffline) {
// Offline: Save status as "Not Submitted" locally
// We'll implement this in the next step
} else {
// Online: Attempt to submit to the backend
// We'll implement this in subsequent steps
}
};

What We're Setting Up:

  • Branching Logic: Clear separation between offline and online submission paths
  • Foundation for Error Handling: Structure ready for try/catch blocks
  • Placeholder Comments: Clear indicators of what we'll implement in each branch

3.2: Implement Offline Submission

Now let's implement the offline submission path with proper error handling:

src/pages/ReviewSubmit.jsx
if (isOffline) {
// Offline: Save status as "Not Submitted" locally
try {
await tripCollection.update({
id: trip.id,
status: "Not Submitted",
step: 4, // Mark as completed step 4 (review)
});
navigate("/offline-confirm");
} catch (error) {
console.error("Error saving trip offline:", error);
setError("Failed to save trip for offline submission. Please try again.");
}
} else {
// Online: Attempt to submit to the backend
// We'll implement this in subsequent steps
}

Understanding Offline Submission:

  1. Status Setting: Mark as "Not Submitted" to indicate it needs server submission later
  2. Step Tracking: Set step to 4 to mark the review process as complete
  3. Error Handling: Catch and display user-friendly error messages
  4. Navigation: Direct to offline confirmation page

3.3: Prepare Server Submission Data

Let's add the data preparation logic for server submission. The catches state variable is already available from Lesson 6:

src/pages/ReviewSubmit.jsx
} else {
// Online: Attempt to submit to the backend
try {
// Prepare submission data with trip and associated catches
const submissionData = {
trip: trip,
catches: catches,
};
// Server request will be added in next step
} catch (error) {
// Error handling will be completed in next step
console.error("Error submitting trip online:", error);
}
}

Data Structure Overview:

The server receives a comprehensive JSON object containing:

  • trip: Complete trip details (dates, times, weather, status)
  • catches: Array of all associated catch records for the trip

3.4: Implement Server Request

Now let's add the actual server request using the fetch API:

src/pages/ReviewSubmit.jsx
  } else {
// Online: Attempt to submit to the backend
try {
// Prepare submission data with trip and associated catches
const submissionData = {
trip: trip,
catches: catches,
};

const response = await fetch("/api/trips", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(submissionData),
});

if (!response.ok) {
const status = response.status;
const statusText = response.statusText ? response.statusText.trim() : "";
const errorDetail = statusText
? `${status} ${statusText}`
: `${status} Server error occurred`;

console.error("Server submission failed:", errorDetail);
setError(`Server submission failed: ${errorDetail}`);
return;
}
} catch (error) {
// Error handling will be completed in next step
console.error("Error submitting trip online:", error);
}
}
};

// Success handling will be added in next step

Understanding the Server Request:

  1. Endpoint: /api/trips (proxied to JSON server at port 3002)
  2. Method: POST to create a new trip record
  3. Headers: Content-Type specifies JSON data
  4. Body: Stringified submission data object
  5. Error Handling: Check response status and provide detailed error messages

3.5: Handle Server Success

Finally, let's implement the success path when server submission completes:

src/pages/ReviewSubmit.jsx
    } else {
try {
const submissionData = {
trip: trip,
catches: catches,
};

const response = await fetch("/api/trips", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(submissionData),
});

if (!response.ok) {
const status = response.status;
const statusText = response.statusText
? response.statusText.trim()
: "";
const errorDetail = statusText
? `${status} ${statusText}`
: `${status} Server error occurred`;

console.error("Server submission failed:", errorDetail);
setError(`Server submission failed: ${errorDetail}`);
return;
}

// If server submission is successful, update local status to "submitted"
await tripCollection.update({
id: trip.id,
status: "submitted",
step: 4,
});
navigate("/online-confirm");
} catch (error) {
// Error handling will be completed in next step
console.error("Error submitting trip online:", error);
// Catch network errors or other issues with the fetch call
console.error("Error submitting trip online:", error);
setError(
"Failed to submit trip. Check your internet connection or try saving offline.",
);
}

Complete Implementation Summary:

Now that we've implemented all the steps, here's what our enhanced submission logic accomplishes:

  1. Offline Path: Simple local save with "Not Submitted" status and proper error handling
  2. Online Path:
    • Prepare submission data combining trip details and associated catches
    • Submit complete data structure to server using fetch API
    • Handle detailed error responses with status codes and messages
    • If successful: Update local status to "submitted"
    • If server fails: Display error message to user
  3. Progressive Enhancement: Each step builds upon the previous one, making it easier to debug and understand

Step 4: Testing Server Submission

4.1: Test Online Submission

Continuing from lesson 6 where we saved a trip offline, let's now test submitting it online:

  1. Ensure JSON server is running: Check that npm run server is still active in your terminal

  2. Navigate to the Home page: You should see one trip with status "READY TO SUBMIT" (from lesson 6)

  3. Change network settings: In browser dev tools (F12), go to Network tab and change from "Offline" to "No throttling"

  4. Click on the saved trip: This will take you back to the Review and Submit page

  5. Verify online status: You should now see the green "Submit" button instead of the blue "Save" button

  6. Click the Submit button: Submit the trip to the server

  7. View success page: You should see the success confirmation:

    Success Page

  8. Check server data: Open a new browser tab and navigate to http://localhost:3002/trips

  9. Return to the app: Go back to your app tab and click the Home button

  10. Verify submitted status: Your trip should now show with "SUBMITTED" status:

    Submitted Status

Expected Results:

  • Trip data appears in the JSON server at http://localhost:3002/trips
  • User is navigated to /online-confirm success page
  • Local storage shows status as "submitted"
  • Trip card on homepage displays green "SUBMITTED" status

4.2: Test Offline Mode

Let's test creating a new trip entirely offline:

  1. Go offline: Use browser dev tools (F12 → Network → Offline)
  2. Start from Home: Navigate to the Home page and click "Start New Trip"
  3. Complete the trip workflow:
    • Fill out the Start Trip form
    • Add catches in the Catch Log
    • Complete the End Trip form
    • Navigate to the Review page
  4. Verify offline status: Notice the blue "Save" button appears instead of green "Submit"
  5. Click save: Trip should save locally as "Not Submitted"
  6. Return to Home: Click the Home button to see your trip with "READY TO SUBMIT" status
  7. Check server: Open a new tab to http://localhost:3002/trips - no new data should appear

4.3: Test Server Failure Gracefully

  1. Stop the JSON server: Terminate the npm run server process
  2. Try to submit online: The app should detect server failure
  3. Verify fallback: Should save locally as "Not Submitted" and show error message

Expected Behavior:

  • ✅ Graceful degradation when server is unavailable
  • ✅ User feedback about server status
  • ✅ Data never lost - always saved locally

Step 5: Viewing Submitted Data

5.1: Inspect Server Data

Visit http://localhost:3002/trips in your browser to see all submitted trips in JSON format:

[
{
"trip": {
"id": "123e4567-e89b-12d3-a456-426614174000",
"tripDate": "2024-01-15",
"startTime": "06:00",
"startWeather": "Sunny",
"endTime": "14:00",
"endWeather": "Cloudy",
"status": "submitted"
},
"catches": [
{
"species": "Bass",
"weight": 3.5,
"length": 14,
"tripId": "123e4567-e89b-12d3-a456-426614174000"
}
],
"id": 1
}
]

5.2: Monitor Local vs Server Data

Compare what's stored locally (IndexedDB) versus what's submitted to the server:

  • Local data: Always present for offline capability
  • Server data: Only present for successfully submitted trips
  • Status field: Indicates whether trip needs server submission

Conclusion

You have successfully implemented a robust submission system that handles both online and offline scenarios! Your RADFish application now:

Key Accomplishments:

  • Dual-mode submission: Automatically submits to server when online, saves locally when offline
  • Graceful degradation: Falls back to offline mode when server is unavailable
  • Data persistence: Ensures user data is never lost regardless of connectivity
  • User feedback: Provides clear visual indicators of submission status
  • Error handling: Manages server failures without breaking the user experience

Your application now provides a production-ready experience that works reliably in any network condition, making it suitable for field work where connectivity may be intermittent.