Skip to main content

Command Palette

Search for a command to run...

Capital Quest India

From Localhost to Production: Deploying React + json-server on Vercel & Render

Published
β€’4 min read
Capital Quest India

Objective: Personal Growth πŸ’―

πŸ§‘β€πŸ’» Source Code

πŸš€ Live on

Project Overview

I recently built a fun learning project β€” a quiz game to test knowledge of Indian states and their capitals. But instead of just focusing on UI, I wanted to practice:

  • useReducer for complex state

  • API integration

  • Backend with json-server

  • Deployment (Vercel + Render)

  • Performance optimizations

Development Journey

Phase 1: Project Setup & Structure

  • Initialized React project with Vite

  • Set up component structure:

      src/
        β”œβ”€β”€ components/
        β”‚   β”œβ”€β”€ FinishScreen.jsx
        β”‚   β”œβ”€β”€ Questions.jsx
        β”‚   β”œβ”€β”€ Main.jsx
        β”‚   β”œβ”€β”€ Progress.jsx
        |   β”œβ”€β”€ Quiz.jsx
        β”‚   β”œβ”€β”€ StartScreen.jsx
        β”‚   └── Header.jsx
        β”œβ”€β”€ main.jsx
        └── index.css
    

Phase 2: Core Quiz Logic Implementation

  • Question Generation: Built logic to randomly select states and generate capital options

  • Answer Validation: Implemented correct/incorrect answer checking

  • Progress Tracking: Added question counter and navigation

  • Option Randomisation: Ensured capital options are shuffled each time

      // Initial simple state
      const [currentQuestion, setCurrentQuestion] = useState(0);
      const [score, setScore] = useState(0);
    
      // Evolved to useReducer for better state management 😎
      const [state, dispatch] = useReducer(quizReducer, initialState);
    

Phase 3: useReducer Integration

  • Defined all possible actions (dataReceived, dataFailed, start, nextQuestion, restart, finish, newAnswer)

  • Created a centralised reducer function

  • Replaced multiple useState calls with a single useReducer

Phase 4: Scoring & Persistence

  • Current score calculation

  • High score tracking

  • localStorage integration for high score persistence (code below)

      function fetchLocalStorage() {
        const stored = localStorage.getItem("capitalQuestHighScore");
        const retrivedHighScore = JSON.parse(stored);
        return retrivedHighScore;
      }
    

Phase 5: UI/UX Enhancement

  • Created a responsive design for mobile devices

  • Styled components with CSS Flexbox/Grid

Phase 6: Testing & Refinement

  • Tested quiz flow from start to finish

  • Verified score calculations

  • Checked localStorage persistence across sessions

  • Tested on multiple devices and screen sizes

  • Fixed edge cases (last question, restart functionality)

Challenges & Solutions

Challenge 1: Complex State Management

Problem: Managing multiple interdependent state variables (current question index, score, etc) became difficult with multiple useState hooks. State updates were scattered across components, making debugging challenging.

Solution:

  • Migrated to useReducer for centralised state management

  • Created a single source of truth with predictable state updates

//1. InitialState object for convenience
const initialState = {
  questions: [],
  options: [],
  status: "loading",
  index: 0,
  points: 0,
  answer: null,
  highScore: 0
};

//2. Destructuring as I need to use this data often
const [{ questions, status, index, answer, points, highScore }, dispatch] =
    useReducer(reducer, initialState);

//3. Reducer function
function reducer(state, action) {
  switch (action.type) {

    case "start":
      return {
        ...state,
        highScore: fetchLocalStorage(),
        status: "active",
      };
    case "nextQuestion":
      return {
        ...state,
        answer: null,
        index: state.index + 1,
      };
    //and it goes on for other 'actions'
   }
}

Challenge 2: Randomising Answer Options

Problem: Correct answer appeared in the same position, making the quiz predictable.

Solution: Created an algorithm to select 3 random incorrect capitals, combine with the correct answer

Challenge 3: High Score Persistence

Problem: High scores reset when the page refreshes.

Solution: Implemented localStorage with useEffect hooks to load high score on mount and save when exceeded. Added error handling for localStorage access issues.

 useEffect(function () {
    const storedHs = Number(fetchLocalStorage() || 0);
    if(storedHs < highScore) localStorage.setItem("capitalQuestHighScore", JSON.stringify(highScore));
  },[highScore]);

Challenge 5: Quiz Flow Edge Cases

Problem: Bugs with the last question, restart functionality, and allowing progression without answer selection.

Solution: Added conditional logic for last question detection, comprehensive restart quiz action, and disabled next button until answer is selected.

πŸš€ Deployment Strategy

Frontend(Vercel) + Backend (Render)

I used json-server to mock APIs.

{
  "questions": [
    { "state": "Assam", "capital": "Dispur" },
    { "state": "Bihar", "capital": "Patna" }
  ]
}
  • Create Web Service

  • Start command:

json-server db.json --host 0.0.0.0 --port 10000

Benefits Realized

  • Predictability: All state changes go through the reducer, making it easy to trace bugs

  • Testability: Reducer is a pure function, easy to unit test

  • Maintainability: Adding new states or actions is straightforward

  • Debugging: Redux DevTools can be used with useReducer for time-travel debugging

Acknowledgments

Educational Resources

Development Tools

  • React, Vite

  • Vercel & Render

  • VS Code

  • Chrome DevTools

  • ChatGPT - AI assistance for resolving specific styling and structural challenges