I am creating a Next.js app that uses a Flask API for the backend. I have a Main component that displays fetched data from the Flask API and displays it. However, I struggle with one issue in the beginning. Whenever I deploy my Flask API after my Next.js app, the Next.js app calls render the object that is supposed to store the fetched data as null, meaning that nothing is returned from the API, causing an error. However, whenever I refresh the app afterwards or start my Flask API before Next.js app, it works fine. I think this is because the Flask API hasn't fully been deployed or hosted whenever I run it after my Next.js app but I don't know how to fix this issue. I am using AWS for depositing data into S3 buckets and DynamoDB for the database storage.
I tried running it afterwards with some time and just waiting before loading it, using a debouncer (which is still in my code because it helped with reducing API calls), and other things. I still don't know how to fix it.
import React, { useState } from 'react'
import axios from 'axios'
import Image from 'next/image'
import SectionColumn from './SectionColumn'
import Loading from './Loading'
const MainLayout = ({isLoading, menuData} : {isLoading: boolean, menuData: string | any}) => {
console.log(menuData)
if (isLoading) {
return (
<div className='flex justify-center items-center h-full'>
<div className="loader"></div> {/* This is where your GSAP animation will be applied */}
</div>
);
}
if (!menuData || menuData === 'Unavailable' ) {
return <div className='flex flex-col items-center justify-center h-full text-gray-200'>
<Image src='/texas-cleared-logo.png' width={128} height={128} alt='Longhorns Logo'/>
<h2>Sorry, but this is not open! Try again later!</h2>
</div>
}
return (
<div className='w-3/4 my-12'>
<div className='hidden lg:flex flex-row justify-between items-start gap-28 mx-4'>
{Object.entries(menuData).map(([mealType, sections]: [string, any]) => (
<SectionColumn key={mealType} sectionName={mealType} sections={sections} />
))}
</div>
<div className='lg:hidden flex flex-col justify-around items-center gap-12'>
{Object.entries(menuData).map(([mealType, sections]: [string, any]) => (
<SectionColumn key={mealType} sectionName={mealType} sections={sections} />
))}
</div>
</div>
)
}
export default MainLayout
import axios from 'axios'
const API_BASE_URL = 'http://127.0.0.1:5000'
export const fetchDiningHalls = async () => {
try {
const response = await axios.get(`${API_BASE_URL}/return_location_halls`)
return response.data.dining_halls
} catch (error) {
console.error(`Error fetching data`, error)
return []
}
}
export const fetchDatesForDiningHall = async (diningHall: string) => {
try {
const response = await axios.get(`${API_BASE_URL}/return_dates/${diningHall}`);
return response.data.dates
} catch (error) {
console.error(`Error fetching dates for dining hall ${diningHall}:`, error)
return []
}
}
export const fetchMenuDataForJ2Dining = async () => {
try {
const response = await axios.get(`${API_BASE_URL}/return_meals/J2 Dining/Wednesday, June 31`);
return response.data;
} catch (error) {
console.error(`Error fetching menu data for dining hall ${} on date ${}:`, error);
return null;
}
};
export const fetchMenuData = async (diningHall: string, date: string) => {
try {
const response = await axios.get(`${API_BASE_URL}/return_meals/${diningHall}/${date}`, {
params: { hall: diningHall, date: date }
});
return response.data;
} catch (error) {
console.error(`Error fetching menu data for dining hall ${diningHall} on date ${date}:`, error);
return null;
}
};
export const handleUpvote = async (itemName: string, sectionName: string) => {
}
export const handleDownvote = async (itemName: string, sectionName: string) => {
}
from flask import Flask, jsonify
from flask_cors import CORS
import boto3
import os
from dotenv import load_dotenv
load_dotenv()
app = Flask(__name__)
cors = CORS(app)
TABLE_NAME = os.environ['TABLE_NAME']
db = boto3.resource('dynamodb')
table = db.Table(TABLE_NAME)
def get_latest_data():
try:
response = table.scan()
items = response.get('Items', [])
if not items:
print('No items available!')
items_sorted = sorted(items, key=lambda x: x['timestamp'], reverse=True)
latest_data = list(items_sorted[0]['data'].values())[0]
return latest_data
except Exception as e:
print(str(e))
return None
@app.route('/')
def home():
return jsonify({'message': 'Hello World!'})
@app.route('/return_dates/<string:dining_hall>', methods=['GET'])
def return_dates(dining_hall):
data = get_latest_data()
if data is None:
return jsonify({'error': 'No data available'}, 200)
dates = data[dining_hall]['dates']
return jsonify({'dates': dates})
@app.route('/return_location_halls', methods=['GET'])
def return_location_halls():
data = get_latest_data()
if data is None:
return jsonify({'error': 'No data available'}, 200)
dining_halls = list(data.keys())
return jsonify({'dining_halls': dining_halls})
@app.route('/return_meals/<string:dining_hall>/<string:date>', methods=['GET'])
def return_meals_dictionary(dining_hall, date):
data = get_latest_data()
if data is None:
return jsonify({'error': 'No data available'}, 200)
menu_items = data[dining_hall]['menus']
for index, item in enumerate(menu_items):
if list(item.keys())[0] == date:
return jsonify({'menu_items': menu_items[index][date]})
return jsonify({'menu_items': menu_items})
if __name__ == '__main__':
app.run(debug=True)
@app.route('/handle_upvote/<string:dining_hall>/<string:item_name>', methods=['POST'])
def handle_upvote(dining_hall, item_name):
pass
@app.route('/handle_downvote/<string:dining_hall>/<string:item_name>', methods=['POST'])
def handle_downvote(dining_hall, item_name):
pass
You can add a retry mechanism in your data fetching logic to make sure the API is available before making requests.
import axios from 'axios';
const API_BASE_URL = 'http://127.0.0.1:5000';
const MAX_RETRIES = 5;
const RETRY_DELAY = 1000;
const fetchWithRetry = async (url, retries = MAX_RETRIES) => {
try {
const response = await axios.get(url);
return response.data;
} catch (error) {
if (retries > 0) {
console.warn(`Retrying... (${MAX_RETRIES - retries + 1}/${MAX_RETRIES})`);
await new Promise(res => setTimeout(res, RETRY_DELAY));
return fetchWithRetry(url, retries - 1);
} else {
console.error('Max retries reached');
throw error;
}
}
};
export const fetchDiningHalls = async () => {
try {
const data = await fetchWithRetry(`${API_BASE_URL}/return_location_halls`);
return data.dining_halls;
} catch (error) {
console.error('Error fetching dining halls', error);
return [];
}
};
export const fetchDatesForDiningHall = async (diningHall) => {
try {
const data = await fetchWithRetry(`${API_BASE_URL}/return_dates/${diningHall}`);
return data.dates;
} catch (error) {
console.error(`Error fetching dates for dining hall ${diningHall}:`, error);
return [];
}
};
export const fetchMenuData = async (diningHall, date) => {
try {
const data = await fetchWithRetry(`${API_BASE_URL}/return_meals/${diningHall}/${date}`);
return data.menu_items;
} catch (error) {
console.error(`Error fetching menu data for dining hall ${diningHall} on date ${date}:`, error);
return null;
}
};
@app.route('/return_dates/<string:dining_hall>', methods=['GET'])
def return_dates(dining_hall):
data = get_latest_data()
if data is None:
return jsonify({'error': 'No data available', 'dates': []}), 200
dates = data.get(dining_hall, {}).get('dates', [])
return jsonify({'dates': dates})
@app.route('/return_location_halls', methods=['GET'])
def return_location_halls():
data = get_latest_data()
if data is None:
return jsonify({'error': 'No data available', 'dining_halls': []}), 200
dining_halls = list(data.keys())
return jsonify({'dining_halls': dining_halls})
@app.route('/return_meals/<string:dining_hall>/<string:date>', methods=['GET'])
def return_meals(dining_hall, date):
data = get_latest_data()
if data is None:
return jsonify({'error': 'No data available', 'menu_items': []}), 200
menu_items = data.get(dining_hall, {}).get('menus', [])
for item in menu_items:
if date in item:
return jsonify({'menu_items': item[date]})
return jsonify({'menu_items': []})