Hi All,
I was looking for a way to get a current users’ timezone.
I managed to do so, by using the v2 components with a javascript function:
if "user_tz_text" not in st.session_state:
JS = """
export default function getUserTimezone(component) {
const { setStateValue, parentElement, data } = component;
const userTimezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
setStateValue('value', userTimezone);
return userTimezone;
}
"""
tz_component = st.components.v2.component(
"get_tz",
js=JS,
)
result = tz_component()
st.session_state["user_tz_text"] = result.value
Now this works, but I always get an error in the console when loading the app in the browser for the first time, or when refreshing. “AttributeError: ‘ComponentResult’ object has no attribute ‘value’” (originating from that last line of code).
Strangely the app continues and it just works, as if ignoring the exception.
I thought that I should wait for the app to ‘complete’ the javascript.
If I change the lower part of the code to (It’s naive, I know):
if "value" in result:
st.session_state["user_tz_text"] = result.value
else:
st.rerun()
Then I can see that I have to wait seconds for the app to be available, it hangs and keeps refreshing for a while.
Can you specify how to resolve this problem, or how to properly use the components to return a value?
I’m using the latest streamlit 1.58.0, running locally on windows.
Hey, thanks for your question and for sharing your code!
This is a classic gotcha with custom components: on the first run, the component’s state value (result.value) isn’t set yet, so trying to access it raises an AttributeError. This happens because the JS hasn’t sent the value back to Python yet—Streamlit reruns the script after the value is set, and only then does result.value exist. This is expected behavior for v2 components.
The recommended way is to always register a callback for your state or trigger values using the on_<key>_change pattern. This ensures the attribute is always present, even before the JS sets it, and avoids the error. For example:
import streamlit as st
JS = """
export default function getUserTimezone(component) {
const { setStateValue } = component;
const userTimezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
setStateValue('value', userTimezone);
}
"""
tz_component = st.components.v2.component("get_tz", js=JS)
def on_value_change():
pass # You can update session_state here if you want
result = tz_component(on_value_change=on_value_change)
st.write("User timezone:", getattr(result, "value", "Loading..."))
This way, result.value will always exist (it will be None until set), and you won’t get the AttributeError. For more details, see the official docs on state and triggers and the example for text input.
Sources:
So it seems that the javascript runs async from the python code, and it must wait for the javascript to run and return a values.
The provided answer by the AI doesnt really help with that; I need to manually put a while loop or st.rerun to wait for the javascript to return a value. There is no point in setting the timezone to “None” by default, the rest of my code is of no value until then. This takes seconds to complete…
Instead I went to using a third party component:
https://github.com/toolittlecakes/streamlit_js
from streamlit_js import st_js_blocking
jscode = """{
const userTimezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
return userTimezone
};"""
timezone = st_js_blocking(code=jscode)
st.session_state["user_tz_text"] = timezone
This works perfectly and allows streamlit to wait for the javascript to complete and return the timezone value; which only takes a second.
Can such logic be included in Streamlit itself? It seems a very obvious use case.
Streamlit already implements such a feature and that information is included into the context attribute.
Here is a minimal code example to test:
import streamlit as st
#...
st.write(st.context.timezone)
st.write(st.context.timezone_offset)
And this returns the following:
Take a look at the documentation page for the complete list of information include in the st.context: