Solve - Redirecting to TrainingList After Saving Training Data

 When building a Single Page Application (SPA) with React on the frontend and an Express server with Sequelize on the backend, one common challenge is handling navigation and redirects properly after switching from server-side rendering (Firstly, I developed the app with ejs) to an API-based architecture. In this post, I’ll walk through how I tackled an issue where saving training data was incorrectly redirecting to the FoodList page instead of the desired TrainingList page. Let’s dive into the problem, the solution, and the final implementation!


The Problem

In the original setup, my Express server used res.render to serve EJS templates and res.redirect to navigate users to different pages after certain actions—like saving a new training entry. For example:

// Before:
Server-side redirect

res.redirect('/foodlist');

However, I refactored the application to use an API-based approach, replacing res.render and res.redirect with res.json to return JSON responses. This shift meant the server no longer controlled navigation. Navigation responsibility moved to the React frontend. After this change, saving a training entry still redirected to /foodlist instead of /traininglist, which was not the intended behavior.

Here’s what I found in the controllers:

  • homeController.ts: Still had leftover res.render calls (e.g., res.render('index', { ... })).
  • calorieController.ts: The saveTraining function was returning a JSON response but lacked clear redirection logic.

The goal? Ensure that after saving a training entry, the user is redirected to the TrainingList tab or page.


Step 1: Eliminate res.render and Standardize API Responses

First, I audited the codebase to confirm that all res.render calls were removed. In homeController.ts, I found four instances:

  • getHomePage: res.render('index', { ... })
  • getLoginPage: res.render('login', { ... })
  • getSaveFoodPage: res.render('save-food', { ... })
  • getSaveTrainingPage: res.render('save-training', { ... })

I replaced these with res.json to return data instead of rendering views. For example:

// Before 

export const getHomePage = (req: Request, res: Response) => { res.render('index', { title: 'Home' }); };
 
// After 
export const getHomePage = (req: Request, res: Response): void => { res.status(200).json({ success: true, message: 'Welcome to the home page' }); };

Next, I updated calorieController.ts to ensure the saveTraining function returned a JSON response with a redirect hint:

export const saveTraining = async (req: Request, res: Response): Promise<void> => 
    try
        const trainingData = req.body; 
        await Training.create(trainingData); // Sequelize model 
        res.status(201).json({ success: true, message
        'Training saved successfully', redirect: '/traininglist', // Hint for the client }); 
    }catch (error) {
         res.status(500).json({ success: false, message: 'Error saving training', error: error.message, });    
    
};

This ensures the server provides a clear signal to the client about where to navigate next.


Step 2: Handle Redirects on the Client Side

Since the server no longer performs redirects, the React frontend needs to handle navigation based on the API response. I used React Router’s useNavigate hook to programmatically redirect the user.

Here’s how I updated the TrainingForm.tsx component:

What’s Happening Here?

  • Form Submission: When the user submits the form, an API request is sent to /api/trainings/save-training.
  • Response Handling: The response includes a redirect field (e.g., /traininglist).
  • Navigation: After displaying a success message for 1 second, navigate('/traininglist') moves the user to the TrainingList page.

This approach keeps the client in control of navigation, aligning with SPA best practices.


Step 3: Tab-Based Navigation

If your app uses a tabbed interface (e.g., FoodList and TrainingList as tabs within a single page), you might prefer updating the active tab instead of navigating to a new route. Here’s how I handled this in App.tsx:

How It Works

  • URL Sync: The tab query parameter (e.g., ?tab=training) controls the active tab.
  • Persistence: Reloading the page preserves the selected tab.
  • User Experience: Saving training data switches to the TrainingList tab seamlessly.

Step 4: Verify the Fix

After applying these changes:

  1. Submit a new training entry via TrainingForm.
  2. See the success message for 1 second.
  3. Get redirected to /traininglist (or ?tab=training if using tabs).
  4. Confirm the app no longer redirects to FoodList.

I tested this locally, and it worked perfectly. No more accidental FoodList redirects!


Key Takeaways

  • API Transition: Moving from res.render/res.redirect to res.json shifts navigation control to the client.
  • Client-Side Navigation: Use useNavigate for route changes or query parameters for tabbed interfaces.
  • Type Safety: Ensure TypeScript types (e.g., void | Response) align with the new API structure.
  • User Feedback: Display success messages before redirecting to improve UX.

This refactoring not only fixed the redirect issue but also made the app more modular and aligned with modern SPA practices. If you’re working on a similar project, I hope this guide helps you navigate (pun intended) the transition smoothly!

Comments

Popular posts from this blog

@ModelAttribute vs @RequestBody in Validation

Side Project(a self-imposed 3-day "Hackathon" challenge)

Google: The King is Back