2

Please help me! I'm building a simple stock search app using Flask (backend) and plain HTML/JS (frontend). When the user enters a ticker, the app:

Send a GET request to my Flask backend (/search)

Display the stock data returned from the Tiingo API

After successful display, send a POST request to /add_to_history to store the ticker in a SQLite database

The GET request works correctly — the data displays as expected in my table.

The problem: As soon as the POST request (/add_to_history) returns a 200 response, the table that was displaying the fetched stock data immediately disappears from the page. There are no errors in the console, and both network requests return status 200. The UI simply hides the content right after the POST call finishes. Here is my index.html code

<!DOCTYPE html>
<html lang="en">
 <head>
 <meta charset="UTF-8">
 <meta name="viewport" content="width=device-width, initial-scale=1.0">
 <link rel="stylesheet" href="style.css">
 <title>Stock App</title>
 <script>
 window.stockData = {
 "companyOutLook": null,
 "stockSummary": null
 }
 function onBtnClick(event) {
 event.preventDefault();
 const inputField = document.getElementById('stockTicker');
 const tickerSymbol = inputField.value;
 
 fetch(`http://127.0.0.1:5000/search?ticker=${encodeURIComponent(tickerSymbol)}`)
 .then(response => {
 if (!response.ok) {
 throw new Error(`HTTP error! status: ${response.status}`);
 }
 return response.json();
 })
 .then(data => {
 console.log('Data received:', data);
 // Check if response contains an error
 if (data.error) {
 hideTabsAndTable();
 showErrorMessage();
 return;
 }
 // Store data for later use
 if (Array.isArray(data) && data.length > 0) {
 window.stockData.companyOutLook = data[0];
 if (data.length > 1) {
 window.stockData.stockSummary = data[1];
 }
 else {
 window.stockData.stockSummary = null;
 }
 
 // Show tabs and table when data is available
 hideErrorMessage();
 
 showTabsAndTable();
 showCompanyOutlook();
 // add to sqlite history
 fetch(`http://127.0.0.1:5000/add_to_history`, {
 method: 'POST',
 headers: {
 'Content-Type': 'application/json',
 },
 body: JSON.stringify({ticker: tickerSymbol}),
 })
 .then(response => {
 console.log('Response:', response);
 
 })
 .catch(error => {
 console.error('Error:', error);
 });
 } 
 else {
 window.stockData.companyOutLook = null;
 window.stockData.stockSummary = null;
 hideTabsAndTable();
 showErrorMessage();
 }
 
 })
 .catch(error => {
 hideTabsAndTable();
 showErrorMessage();
 });
 }
 function showTabsAndTable() {
 const tabs = document.querySelector('.tabs');
 const table = document.querySelector('.content');
 if (tabs) tabs.style.display = 'flex';
 if (table) table.style.display = 'table';
 }
 function hideTabsAndTable() {
 const tabs = document.querySelector('.tabs');
 const table = document.querySelector('.content');
 if (tabs) tabs.style.display = 'none';
 if (table) table.style.display = 'none';
 }
 function showCompanyOutlook() {
 const table = document.querySelector('.content');
 table.innerHTML = '';
 
 const rows = [
 'Company Name',
 'Ticker Symbol',
 'Exchange Code',
 'Start Date',
 'Description'
 ];
 const dataNames = [
 "name",
 "ticker",
 "exchangeCode",
 "startDate",
 "description"
 ] 
 if (window.stockData.companyOutLook) {
 rows.forEach((header, i) => {
 const row = document.createElement('tr');
 const headerCell = document.createElement('td');
 headerCell.className = 'content-header';
 headerCell.textContent = header;
 const dataCell = document.createElement('td');
 dataCell.className = 'content-data';
 const value = window.stockData.companyOutLook[dataNames[i]] ?? '';
 dataCell.textContent = value;
 row.appendChild(headerCell);
 row.appendChild(dataCell);
 table.appendChild(row);
 });
 }
 else {
 table.innerHTML = "<tr><td colspan='2'><p>Error: No record has been found, please enter a valid symbol.</p></td></tr>";
 }
 
 
 
 // Update active tab styling
 updateActiveTab(0);
 }
 function showStockSummary() {
 const table = document.querySelector('.content');
 table.innerHTML = '';
 
 const rows = [
 'Ticker Symbol',
 'Trading Day',
 'Previous Closing Price',
 'Opening Price',
 'High Price',
 'Low Price',
 'Last Price',
 'Change',
 'Change Percent',
 'Number of Shares Traded'
 ];
 if (window.stockData.stockSummary) {
 const summary = window.stockData.stockSummary;
 const last = Number(summary.last ?? summary.tngoLast ?? 0);
 const prevClose = Number(summary.prevClose ?? 0);
 const change = prevClose ? last - prevClose : 0;
 const changePercent = prevClose ? (change / prevClose) * 100 : 0;
 
 const tradingDay = summary.timestamp
 ? String(summary.timestamp).split('T')[0]
 : '';
 const data = [
 summary.ticker ?? '',
 tradingDay,
 prevClose || '',
 summary.open ?? '',
 summary.high ?? '',
 summary.low ?? '',
 last || '',
 (change > 0 ? "+" + change.toFixed(2) : change < 0 ? change.toFixed(2) : "0") || '',
 (changePercent > 0 ? "+" + changePercent.toFixed(2) + "%" : changePercent < 0 ? changePercent.toFixed(2) + "%" : "0%") || '',
 summary.volume ?? '',
 ];
 rows.forEach((header, i) => {
 const row = document.createElement('tr');
 const headerCell = document.createElement('td');
 headerCell.className = 'content-header';
 headerCell.textContent = header;
 const dataCell = document.createElement('td');
 dataCell.className = 'content-data';
 dataCell.id = `data-${i}`;
 dataCell.textContent = data[i];
 row.appendChild(headerCell);
 row.appendChild(dataCell);
 table.appendChild(row);
 });
 // add image to data 7 and 8
 const data7 = document.getElementById('data-7');
 const data8 = document.getElementById('data-8');
 if (data7 && data7.textContent.includes("+")) {
 data7.innerHTML += "<img src='GreenArrowUP.png' alt='Green Arrow Up' width='10' height='10' />"
 } else if (data7 && data7.textContent.includes("-")) {
 data7.innerHTML += "<img src='RedArrowDown.png' alt='Red Arrow Down' width='10' height='10' />"
 }
 if (data8 && data8.textContent.includes("+")) {
 data8.innerHTML += "<img src='GreenArrowUP.png' alt='Green Arrow Up' width='10' height='10' />"
 } else if (data8 && data8.textContent.includes("-")) {
 data8.innerHTML += "<img src='RedArrowDown.png' alt='Red Arrow Down' width='10' height='10' />"
 }
 }
 else {
 table.innerHTML = "<p>Error: No record has been found, please enter a valid symbol.</p>";
 }
 
 
 // Update active tab styling
 updateActiveTab(1);
 }
 function updateActiveTab(activeIndex) {
 const tabs = document.querySelectorAll('.tabs > div');
 tabs.forEach((tab, index) => {
 if (index === activeIndex) {
 tab.classList.add('active');
 } else {
 tab.classList.remove('active');
 }
 });
 }
 function showErrorMessage() {
 const errorMessage = document.querySelector('.error-message');
 errorMessage.style.display = 'block';
 }
 function hideErrorMessage() {
 const errorMessage = document.querySelector('.error-message');
 errorMessage.style.display = 'none';
 }
 // Initialize tabs with click handlers
 document.addEventListener('DOMContentLoaded', function() {
 const tabs = document.querySelectorAll('.tabs > div');
 tabs[0].addEventListener('click', showCompanyOutlook);
 tabs[1].addEventListener('click', showStockSummary);
 // Search History tab - no action for now
 });
 function clearInput() {
 const inputField = document.getElementById('stockTicker');
 inputField.value = '';
 window.stockData.companyOutLook = null;
 window.stockData.stockSummary = null;
 hideTabsAndTable();
 hideErrorMessage();
 }
 </script>
 </head>
 <body>
 <div class="container">
 <h1>Stock Search</h1>
 <form onsubmit="onBtnClick(event)">
 <span>Enter Stock Ticker Symbol<span class="required">*</span></span>
 <input type="text" id="stockTicker" required>
 <div class="buttons"> 
 <button type="submit" >Search</button>
 <button type="button" onclick="clearInput()">Clear</button></div>
 </form>
 
 </div>
 <div class="divider"></div>
 
 <div class="error-message" style="display: none;">
 <p>Error: No record has been found, please enter a valid symbol.</p>
 </div>
 <div class="tabs" style="display: none;">
 <div>Company Outlook</div>
 <div>Stock Summary</div>
 <div>Search History</div>
 </div>
 <table class="content" style="display: none;">
 <!-- Table rows will be dynamically generated by JavaScript -->
 <tbody id="table-body"></tbody>
 </table>
 </body>
</html>

Here is the app.py file

from flask import Flask, request, jsonify
from flask_cors import CORS
import requests
import sqlite3
app = Flask(__name__)
CORS(app, resources={r"/search*": {"origins": "*"}, r"/add_to_history*": {"origins": "*"}})
API_KEY = "SECRET_API_KEY"
DB_NAME = "search_history.db"
@app.route('/')
def index():
 return "Hello, World!"
def init_db():
 with sqlite3.connect(DB_NAME) as conn:
 conn.execute('''CREATE TABLE IF NOT EXISTS SearchHistory (
 id INTEGER PRIMARY KEY AUTOINCREMENT,
 ticker TEXT NOT NULL,
 timestamp DATETIME DEFAULT CURRENT_TIMESTAMP
 )''')
# initialize the database
# run init_db() when the app starts
init_db()
@app.route('/history', methods=['GET'])
def get_search_history():
 with sqlite3.connect(DB_NAME) as conn:
 conn.row_factory = sqlite3.Row
 rows = conn.execute('SELECT ticker, timestamp FROM SearchHistory LIMIT 10').fetchall()
 results = [dict(row) for row in rows]
 return jsonify(results)
@app.route('/add_to_history', methods=['POST'])
def add_to_history():
 try:
 data = request.get_json()
 if not data:
 return jsonify({'error': 'No JSON data provided'}), 400
 
 ticker = data.get('ticker')
 if not ticker:
 return jsonify({'error': 'Ticker is required'}), 400
 
 with sqlite3.connect(DB_NAME) as conn:
 conn.execute('INSERT INTO SearchHistory(ticker) VALUES (?)', (ticker,))
 conn.commit()
 
 return jsonify({'message': 'Record added to history'}), 200
 except Exception as e:
 return jsonify({'error': str(e)}), 500
def get_history():
 with sqlite3.connect(DB_NAME) as conn:
 conn.row_factory = sqlite3.Row
 rows = conn.execute('SELECT ticker, timestamp FROM SearchHistory LIMIT 10').fetchall()
 results = [dict(row) for row in rows]
 return results
@app.route('/search', methods=['GET'])
def search():
 try:
 ticker = request.args.get('ticker', "")
 # if not ticker is passed, return error message
 if not ticker:
 return jsonify({'error': "No record has been found, please enter a valid symbol" })
 # define a return object
 results = []
 # fetch company outlook tab
 url = f"https://api.tiingo.com/tiingo/daily/{ticker}?token={API_KEY}"
 response = requests.get(url)
 data = response.json()
 if data and data.get('detail', "") != "Not found.":
 results.append(data)
 # fetch stock summary tab
 url = f"https://api.tiingo.com/iex/{ticker}?token={API_KEY}"
 response = requests.get(url)
 data = response.json()
 if data and len(data) > 0:
 results.append(data[0]) 
 # add to history if fetched successfully
 # if results:
 # add_to_history(ticker)
 
 return jsonify(results)
 except Exception as e:
 return jsonify({'error': str(e) })
 
if __name__ == '__main__':
 app.run(debug=True)
asked Dec 1, 2025 at 6:02
1
  • Have you debugged the event handler code for the onClick event? Shot in the blue: At some point in time during the execution an error occured and the greedy catch all block triggers, in the catch block you hide all tabs and change the CSS property style of the error block but you don't actually fill it. Commented Dec 1, 2025 at 7:05

1 Answer 1

0

Unfortunately, I was unable to reproduce the behavior.

However, the described errors can occur due to various strategies used. I assume that an error occurs within a then block, causing the associated catch block to be executed, but the actual source of the error lies within a called function. Using additional try-catch statements could narrow down the search. I have attempted to eliminate potential sources of error by optimizing the code.

I have made the following changes:

  • Data is retrieved using only a single fetch request. A further POST request is unnecessary, as the information is already available.
  • Data is stored within DOM elements. Therefore, parts of the display only need to be rebuilt after a fetch request and not through other interactions, such as navigating within the tabs.
from contextlib import closing
from flask import Flask, g, render_template, request, jsonify
import os
import requests
import sqlite3
app = Flask(__name__)
app.config.from_mapping(
 API_KEY = "SECRET_API_KEY", 
 DATABASE = os.path.join(app.instance_path, "search_history.db")
)
def get_db():
 db = getattr(g,'_database', None)
 if db is None:
 db = g._database = sqlite3.connect(app.config['DATABASE'])
 db.row_factory = sqlite3.Row
 return db
@app.teardown_appcontext
def close_connection(exception):
 db = getattr(g, '_database', None)
 if db is not None:
 db.close() 
os.makedirs(app.instance_path, exist_ok=True)
with app.app_context():
 db = get_db()
 with closing(db.cursor()) as c:
 c.execute('''CREATE TABLE IF NOT EXISTS SearchHistory (
 id INTEGER PRIMARY KEY AUTOINCREMENT,
 ticker TEXT NOT NULL,
 timestamp DATETIME DEFAULT CURRENT_TIMESTAMP
 )''')
 db.commit()
@app.route('/')
def index():
 return render_template('index.html', **locals())
def fetch_api_data(url):
 # It's better to use the token within the header so that 
 # it isn't displayed within the URL in case of errors.
 headers={ 
 'Content-Type': 'application/json', 
 # 'Authorization': f'Token {app.config['API_KEY']}' 
 }
 response = requests.get(url, headers=headers)
 response.raise_for_status()
 data = response.json()
 if details := data.get('details'):
 raise Exception(details)
 return data
def update_and_select_history(ticker):
 db = get_db()
 with closing(db.cursor()) as c:
 c.execute('INSERT INTO SearchHistory(ticker) VALUES (?)', (ticker,))
 c.execute('SELECT ticker, timestamp FROM SearchHistory ORDER BY timestamp DESC LIMIT 10')
 results = [dict(row) for row in c.fetchall()]
 db.commit()
 return results
@app.get('/search')
def search():
 ticker = request.args.get('ticker')
 # if not ticker is passed, return error message
 if not ticker:
 return jsonify({'error': "No record has been found, please enter a valid symbol" })
 try:
 url = f"https://api.tiingo.com/tiingo/daily/{ticker}?token={app.config['API_KEY']}"
 outlook = fetch_api_data(url)
 outlook = outlook or None
 # outlook = {
 # "ticker": "AAPL",
 # "name": "Apple Inc",
 # "exchangeCode": "NASDAQ",
 # "startDate": "1980年12月12日",
 # "endDate": "2019年01月25日",
 # "description": """Apple Inc. (Apple) designs, manufactures and markets mobile communication and ..."""
 # }
 url = f"https://api.tiingo.com/iex/{ticker}?token={app.config['API_KEY']}"
 summary = fetch_api_data(url)
 if isinstance(summary, list) and len(summary) > 0:
 summary = summary[0]
 summary = summary or None
 # summary = {
 # "ticker":"AAPL",
 # "timestamp":"2019年01月30日T10:33:38.186520297-05:00",
 # "quoteTimestamp":"2019年01月30日T10:33:38.186520297-05:00", 
 # "lastSaleTimeStamp":"2019年01月30日T10:33:34.176037579-05:00",
 # "last":162.37,
 # "lastSize":100,
 # "tngoLast":162.33,
 # "prevClose":154.68,
 # "open":161.83,
 # "high":163.25,
 # "low":160.38,
 # "mid":162.67,
 # "volume":0,
 # "bidSize":100,
 # "bidPrice":162.34,
 # "askSize":100,
 # "askPrice":163.0
 # }
 history = update_and_select_history(ticker)
 return jsonify(
 outlook=outlook, 
 summary=summary, 
 history=history
 )
 except Exception as exc:
 print(exc)
 return jsonify(error='Error retrieving data.')
<!DOCTYPE html>
<html lang="en">
 <head>
 <meta charset="UTF-8">
 <meta name="viewport" content="width=device-width, initial-scale=1.0">
 <title>Stock App</title>
 <style>
 hr.divider {
 border: none;
 border-bottom: 1px solid black;
 }
 .tab {
 display: flex;
 flex-wrap: wrap;
 list-style: none;
 border-bottom: 1px solid black;
 padding: 0;
 }
 .tab-item {
 display: inline-block;
 padding: 0.6rem 0;
 padding-right: 1.6rem;
 border-bottom: 2px solid transparent;
 }
 .tab-item.active {
 border-bottom: 2px solid black;
 }
 .tab-pane, .error-message, .results {
 display: none;
 }
 .buttons {
 display: inline-block;
 }
 .active {
 display: block;
 }
 </style>
 </head>
 <body>
 <div class="container">
 <h1>Stock Search</h1>
 <form name="search-form">
 <label for="stockTicker">Enter Stock Ticker Symbol<span class="required">*</span></label>
 <input type="text" name="ticker" id="stockTicker" required />
 <div class="buttons"> 
 <button type="submit">Search</button>
 <button type="reset">Clear</button>
 </div>
 </form>
 </div>
 <hr class="divider" />
 <div class="error-message">
 <p>Error: No record has been found, please enter a valid symbol.</p>
 </div>
 <div class="results">
 <div class="tabs">
 <ul class="tab">
 <li class="tab-item" data-target="tab-outlook">Company Outlook</li>
 <li class="tab-item" data-target="tab-summary">Stock Summary</li>
 <li class="tab-item" data-target="tab-history">Search History</li>
 </ul>
 <div class="tab-content">
 <div class="tab-pane active" id="tab-outlook"></div>
 <div class="tab-pane" id="tab-summary"></div>
 <div class="tab-pane" id="tab-history"></div>
 </div>
 </div>
 </div>
 <script>
 (function() {
 const activateElement = (sel, visible) => {
 const elem = document.querySelector(sel);
 if (visible) { 
 elem.classList.add('active');
 } else {
 elem.classList.remove('active');
 }
 };
 const activateTabPane = (target) => {
 document.querySelectorAll('.tab-item[data-target]').forEach(item => {
 if (item.dataset.target === target) {
 item.classList.add('active');
 } else {
 item.classList.remove('active');
 }
 });
 document.querySelectorAll('.tab-pane[id]').forEach(tabPaneElem => {
 if (tabPaneElem.id !== target) {
 tabPaneElem.classList.remove('active');
 } else {
 tabPaneElem.classList.add('active');
 }
 });
 };
 const showCompanyOutlook = (data) => {
 const tabElem = document.querySelector('#tab-outlook');
 if (!data) {
 tabElem && (tabElem.innerHTML = '<p>No data available.</p>');
 return;
 }
 const rowMapping = {
 name: 'Company Name',
 ticker: 'Ticker Symbol',
 exchangeCode: 'Exchange Code',
 startDate: 'Start Date',
 description: 'Description'
 };
 const html = '<table class="table-outlook"><tbody>'
 + Object.entries(rowMapping).map(([k,v]) => {
 return `
 <tr>
 <th class="content-header" scope="row">${v}</th>
 <td class="content-data">${data[k]}</td>
 </tr>
 `;
 }).join('')
 + '</tbody></table>';
 tabElem && (tabElem.innerHTML = html);
 };
 const showHistory = (data) => {
 if (!data) return;
 
 const html = '</ul>' + data.map(row => {
 return `<li>${row.ticker} (${row.timestamp})</li>`;
 }).join('') + '</ul>';
 const historyPane = document.querySelector('#tab-history');
 historyPane && (historyPane.innerHTML = html);
 };
 const showStockSummary = (data) => {
 const tabElem = document.querySelector('#tab-summary');
 if (!data) {
 tabElem && (tabElem.innerHTML = '<p>No data available.</p>');
 return;
 }
 const last = Number(data.last ?? data.tngoLast ?? 0);
 const prevClose = Number(data.prevClose ?? 0);
 const change = prevClose ? last - prevClose : 0;
 const changePercent = prevClose ? (change / prevClose) * 100 : 0;
 const tradingDay = data.timestamp 
 ? (new Date(data.timestamp)).toLocaleDateString()
 : '';
 const dataMapping = {
 'Ticker Symbol': data.ticker ?? '',
 'Trading Day': tradingDay,
 'Previous Closing Price': prevClose || '',
 'Opening Price': data.open ?? '',
 'High Price': data.high ?? '',
 'Low Price': data.low ?? '',
 'Last Price': last || '',
 'Change': (change > 0 ? "+" + change.toFixed(2) : change < 0 ? change.toFixed(2) : "0") || '',
 'Change Percent': `${changePercent < 0 ? '-' : (changePercent > 0 ? '+' : '')}${changePercent.toFixed(2)}%`, 
 'Number of Shares Traded': data.volume ?? ''
 };
 const html = '<table class="table-summary"><tbody>'
 + Object.entries(dataMapping).map(([k,v], i) => {
 return `
 <tr>
 <th class="content-header" scope="row">${k}</th>
 <td class="content-data" id="data-${i}">${v}</td>
 </tr>
 `;
 }).join('')
 + '</tbody></table>';
 tabElem && (tabElem.innerHTML = html);
 // ... 
 };
 const form = document.querySelector('form[name="search-form"]');
 form.addEventListener('submit', function(event) {
 event.preventDefault();
 const formData = new FormData(this);
 const ticker = formData.get('ticker');
 const url = `/search?ticker=${encodeURIComponent(ticker)}`;
 fetch(url)
 .then(resp => {
 if (!resp.ok) {
 throw new Error(`HTTP-Error: (status=${resp.status})`);
 }
 return resp.json();
 })
 .then(data => {
 if (data.error) {
 throw new Error(data.error);
 }
 const { outlook, summary, history } = data;
 showCompanyOutlook(outlook);
 showStockSummary(summary);
 showHistory(history);
 activateElement('.error-message', false);
 activateElement('.results', true);
 activateTabPane('tab-outlook');
 })
 .catch(error => {
 console.error(error);
 activateElement('.error-message', true);
 activateElement('.results', false);
 });
 });
 form.addEventListener('reset', () => {
 activateElement('.error-message', false);
 activateElement('.results', false);
 });
 const tabItemElems = document.querySelectorAll('.tab-item[data-target]');
 tabItemElems.forEach(tabItemElem => {
 tabItemElem.addEventListener('click', function() {
 activateTabPane(this.dataset.target);
 });
 });
 })();
 </script>
 </body>
</html>
answered Dec 1, 2025 at 22:33
Sign up to request clarification or add additional context in comments.

5 Comments

Did you not encounter the bug that I experienced when running my provided code? Because I still experienced a similar bug when running your solution
No, both versions are running without problems, although I can't test API requests due to a missing key. If parts of the user interface disappear in my version as well, I can't say what the cause is. Are there really no error messages at all?
yes, there are no error messages displayed in the terminal at all. It appears that the page refreshes immediately after I successfully added the data to the database. For the API key, you can get it for free here: api.tiingo.com because If you run the app just to display the retrieved summary without adding it to the database, it works fine.
I've revised my example and compared it with the documentation, other libraries, and projects. Please try it again and give me feedback on what happens or what's displayed in the terminal or the browser's developer console.
Hi, for your update: I fixed the bug. It was due to the page reloading after the data was added to the database, which reset all the state to its initial value. I fixed it by storing all the data and state in localStorage so they will not be lost as the page refreshes. Btw, I really appreciate your help and hope you have a good day.

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.