Strange behavior with st.form and date_input when reading default values from a file

Hello,

You may reproduce the behavior I describe by executing the application code below.

I made a form with two date inputs and a submit button. The ‘start’ and ‘end’ dates are auto-completed based on default values saved in a JSON file (or, if the file is not readable, the ‘start’ date will be 31 days before now, and the ‘end’ date will be today).

With the following code, I expect the application to display the default values when the page is first loaded, and when the page is reloaded after the user has submitted the form, the dates displayed should be the ones that the user has selected.

This works as expected when the following part is commented out, that is when the default values are never read from the file:

    try:
        with open('data_update_date.json') as f:
            data = f.read()
        saved_dates = json.loads(data)
        startDate_default = datetime.datetime.strptime(saved_dates['startDate'], '%Y-%m-%d')
        stopDate_default = datetime.datetime.strptime(saved_dates['stopDate'], '%Y-%m-%d')

    # If the file doesn't exist or is corrupted, input default dates
    except:

However, when the dates are read from the file, every other time the user submits the form, the dates selected by the user will not be considered when the page is reloaded (instead, the dates selected the previous time will be shown). One every other time, the result of the ‘print(startDate)’ will not be the date that the user has selected before last submitting the form, but the date that he had selected the previous time.

I can’t explain that behavior.

import datetime
import json
import streamlit as st

with st.form("dates"):
    start, stop, update_button = st.columns((1, 1, 1))

    # Read the dates from JSON file to autocomplete the input form
    try:
        with open('data_update_date.json') as f:
            data = f.read()
        saved_dates = json.loads(data)
        startDate_default = datetime.datetime.strptime(saved_dates['startDate'], '%Y-%m-%d')
        stopDate_default = datetime.datetime.strptime(saved_dates['stopDate'], '%Y-%m-%d')

    # If the file doesn't exist or is corrupted, input default dates
    except:
        startDate_default = datetime.date.today() - datetime.timedelta(days=31)
        stopDate_default = datetime.date.today()

    # Start and end dates
    startDate = start.date_input('Start Date', startDate_default)
    stopDate = stop.date_input('End Date', stopDate_default)

    print(startDate)

    # Button to send the form
    submitted = update_button.form_submit_button("Update data")

# When a user has submitted the form, update the JSON file with the selected dates so that the dates are displayed the next time the page is loaded
if submitted:
    with open('data_update_date.json', 'w') as file:
        file.write(json.dumps({'startDate': startDate,
                               'stopDate': stopDate}, default=str, indent=2))

The JSON file is as follows:

{
“startDate”: “2021-12-01”,
“stopDate”: “2021-12-22”
}

To reproduce the same behavior with a much simpler application.
When the default date is made with ‘strptime’, the value of ‘startDate’ after the form is submitted will not be memorized every other time. However, when it is set to ‘today()’, the date selected by the user is always reflected.

import datetime
import json
import streamlit as st

with open('data_update_date.json') as f:
    saved_dates = json.load(f)
    # strange behavior:
    startDate_default = datetime.datetime.strptime(saved_dates['startDate'], '%Y-%m-%d')
    # normal behavior:
    # startDate_default = datetime.date.today()

with st.form("dates"):
    start, update_button = st.columns((1, 1))
    startDate = start.date_input('Start Date', startDate_default)
    submitted = update_button.form_submit_button("Update data")

if submitted:
    with open('data_update_date.json', 'w') as file:
        json.dump({'startDate': str(startDate)}, file)

data_update_date.json:

{"startDate": "2022-05-06"}

Apparently this is not a ‘strptime’ problem, because replacing
startDate_default = datetime.datetime.strptime(saved_dates['startDate'], '%Y-%m-%d')
by
startDate_default = datetime.datetime.strptime('2020-10-10', '%Y-%m-%d')
(or any date hardcoded as a string) get rids of the problem.

Hi @phoenix1790 :wave:

Thanks for sharing a reproducible example! I was able to repro the behavior on my end.

The root cause of the issue is that once the submit button is pressed and the data is dumped to JSON, the app does not rerun from top to bottom. Meaning the following code block isn’t run again, thereby using the old value of startDate from memory:

with open('data_update_date.json') as f:
    saved_dates = json.load(f)
    # strange behavior:
    startDate_default = datetime.datetime.strptime(saved_dates['startDate'], '%Y-%m-%d')
    # normal behavior:
    # startDate_default = datetime.date.today()

What you want to do is force a rerun so that your updated json file with the newly written startDate value is read into memory. To do so, define a callback function that updates your json file:

import datetime
import json
import streamlit as st

def update_date():
    with open("data_update_date.json", "w") as file:
        json.dump({"startDate": str(st.session_state.startDate)}, file)

with open("data_update_date.json") as f:
    saved_dates = json.load(f)
    startDate_default = datetime.datetime.strptime(saved_dates["startDate"], "%Y-%m-%d")

with st.form("dates"):
    start, update_button = st.columns((1, 1))
    startDate = start.date_input("Start Date", startDate_default, key="startDate")
    submitted = update_button.form_submit_button("Update data", on_click=update_date)

Best, :balloon:
Snehan

1 Like

That works perfectly ! Thank you very much.

1 Like