Repository: streamlit/streamlit

Issue:
Warning when initialisation st.number_input using state API
### Summary

When using State API st.number_input() always result in a warning: `The widget with key "...." was created with a default value but also had its value set via the Session State API.`

### Steps to reproduce

Code snippet:

```
st.session_state['comp.pagination.chunk_size'] = 5
items_per_page = component.number_input(
        "Genes per page:",
        min_value=1,
        max_value=100,
        key='comp.pagination.chunk_size'
)


```

If applicable, please provide the steps we should take to reproduce the bug:

1. Go ceate a simple app with number_input comp
2. Run `streamlit run app.py`
3. You will see a waring

**Expected behavior:**

Expected: No warning

**Actual behavior:**

Actual: Warning is shown.
![image](https://user-images.githubusercontent.com/72933/137596977-4758e2ad-dcf0-444e-bf48-2dbf8235839c.png)


### Is this a regression?

No

### Debug info

- Streamlit version: 1.00.
- Python version: 3.8.1
- Using Conda
- OS version: MacOS 11.3
- Browser version: Safari

### Additional information

The issue is because `NumberInputMixin.number_input` initializes `value` with not None default:
```
class NumberInputMixin:
    def number_input(
        self,
        label: str,
        min_value: Optional[Number] = None,
        max_value: Optional[Number] = None,
        value=NoValue(),
        ...
```

so the check doesn't work properly, because it assumes that not initialized state corresponds to `None`
```
check_session_state_rules(default_value=value, key=key)
```
Here I cannot pass `value=None` as a workaround because `NumberInputMixin` because then I'll get `All numerical arguments must be of the same type`. 

So expected behaviour is to init `value` with `None`. Then if key exists in state update it from state, otherwise show warning that widget already initialized from session API.




Discussion:
Hi @iromeo,
Thanks for bringing this issue up. It looks like you are setting the value of the number_input widget prior to each run through the following line of code, which doesn't allow for changes to the value through the widget:
`st.session_state['comp.pagination.chunk_size'] = 5`

This is actually intended behavior. The following code should help, where we initialize on the first run. Please let me know should you need additional help!
```python
if 'comp.pagination.chunk_size' not in st.session_state:
    st.session_state['comp.pagination.chunk_size'] = 5

items_per_page = st.number_input(
        "Genes per page:",
        min_value=1,
        max_value=100,
        key='comp.pagination.chunk_size'
)
```
Hi @mayagbarnes,
> The following code should help, where we initialize on the first run

Thank you for the reply. Actually, I do the same lazy initialization and forget to mention it in the issue description. For the first app run (after server started) component key is in any case not in cache, so I omitted 'if' condition in the minimal test case, seems it confused you.

I suppose you didn't check that you suggested example works as you expected. For your example I get the same error:

![image](https://user-images.githubusercontent.com/72933/137865247-388e4428-4f0a-401f-8775-c12d5e0a701c.png)

According to  `st.number_input` implementation details, this cannot work. Please read my explanation in `Additional information` section and check the source code.
Hi @iromeo,

Thanks for your reply! Upon revisiting - I think the intent of the warning is to note duplicated efforts (directly setting `'comp.pagination.chunk_size'` with `st.session_state` when `st.number_input`'s value is already tied to session state via key). 

I did miss that warning reappearing, my mistake! Please see below (better tested 😄) code, and let me know if you are still getting the same warning!
```python
# Directly setting number_input's default value through value parameter, remove session state initialization
  items_per_page = st.number_input(
          "Genes per page:",
          min_value=1,
          max_value=100,
          value=5,
          key='comp.pagination.chunk_size'
  )
```
Hello @mayagbarnes, thank you for your reply.  I know that I could init the component without using State API (as in your example), but this issue is about using state API, so a workaround the inits component without using of state API is not what I'm talking about.

Let me formulate the issue in another way, I hope it will be more clear: 

----

At the moment `st.number_input`  behaviour is not consistent with `st.selectbox` and `st.text_input` terms of initialization via State API:

Select box and text input produce no warnings for:
```
import streamlit as st

if 'comp.pagination.chunk_size' not in st.session_state:
    st.session_state['comp.pagination.chunk_size'] = 5

items_per_page = st.selectbox(
        "Genes per page:",
        options=range(10),
        key='comp.pagination.chunk_size'
)
```

Text input produces a warning for:
```
import streamlit as st
if 'comp.pagination.chunk_size' not in st.session_state:
    st.session_state['comp.pagination.chunk_size'] = 5


items_per_page = st.number_input(
        "Genes per page:",
        min_value=1,
        max_value=100,
        key='comp.pagination.chunk_size'
)

```

And the cause of such inconsistency is in impl difference of `st.selectbox` and `st.number_input`. 

`st.selectbox` constructor:
```
class SelectboxMixin:
    def selectbox(
        self,
        # ..
        index: int = 0,
         # ..
         )
#...
check_session_state_rules(default_value=None if index == 0 else index, key=key)
# ...
```


`st.number_input` constructor:
```
class NumberInputMixin:
    def number_input(
        self,
        # ..
        value=NoValue(),
        # ..
        )
#...
check_session_state_rules(default_value=value, key=key)
# ...
```

Seems it is pretty clear that consistent behaviour for `st.number_input` will be:
```
# ..
check_session_state_rules(default_value=None if value == NoValue() else value, key=key)
# ..
```
Thanks for reporting this @iromeo,

I believe you're right, and the behavior of `st.number_input` is inconsistent with other widgets -- this likely happened simply because of an oversight when we were implementing `st.session_state`.

I'm about to go on vacation so can't promise that I'll have a fix for this out extremely quickly 😅, but I'll be sure to get one out when I return the second week of November (alternatively, it'd be greatly appreciated if you would like to submit a small PR to fix this behavior, as from what I can tell the proposed change at the end of your last comment is indeed the correct fix 🙂).
@vdonato thanks, I'll try
to make a PR for this. Have a good vacation!

Resolving patch:
diff --git a/lib/streamlit/elements/file_uploader.py b/lib/streamlit/elements/file_uploader.py
--- a/lib/streamlit/elements/file_uploader.py
+++ b/lib/streamlit/elements/file_uploader.py
@@ -21,7 +21,7 @@
 from streamlit.logger import get_logger
 from streamlit.proto.FileUploader_pb2 import FileUploader as FileUploaderProto
 from streamlit.report_thread import get_report_ctx
-from streamlit.state.widgets import register_widget, NoValue
+from streamlit.state.widgets import register_widget
 from streamlit.state.session_state import (
     WidgetArgs,
     WidgetCallback,
diff --git a/lib/streamlit/elements/number_input.py b/lib/streamlit/elements/number_input.py
--- a/lib/streamlit/elements/number_input.py
+++ b/lib/streamlit/elements/number_input.py
@@ -98,7 +98,9 @@ def number_input(
         """
         key = to_key(key)
         check_callback_rules(self.dg, on_change)
-        check_session_state_rules(default_value=value, key=key)
+        check_session_state_rules(
+            default_value=None if isinstance(value, NoValue) else value, key=key
+        )
 
         # Ensure that all arguments are of the same type.
         number_input_args = [min_value, max_value, value, step]
diff --git a/lib/streamlit/elements/selectbox.py b/lib/streamlit/elements/selectbox.py
--- a/lib/streamlit/elements/selectbox.py
+++ b/lib/streamlit/elements/selectbox.py
@@ -23,7 +23,7 @@
     WidgetCallback,
     WidgetKwargs,
 )
-from streamlit.state.widgets import register_widget, NoValue
+from streamlit.state.widgets import register_widget
 from streamlit.type_util import Key, OptionSequence, ensure_indexable, to_key
 from streamlit.util import index_
 from .form import current_form_id


Test patch:
diff --git a/lib/tests/streamlit/number_input_test.py b/lib/tests/streamlit/number_input_test.py
--- a/lib/tests/streamlit/number_input_test.py
+++ b/lib/tests/streamlit/number_input_test.py
@@ -14,7 +14,7 @@
 
 """number_input unit test."""
 
-from unittest.mock import patch
+from unittest.mock import MagicMock, patch
 import pytest
 
 import streamlit as st
@@ -234,3 +234,20 @@ def test_inside_column(self):
         self.assertEqual(number_input_proto.label, "foo")
         self.assertEqual(number_input_proto.step, 1.0)
         self.assertEqual(number_input_proto.default, 0)
+
+    @patch("streamlit._is_running_with_streamlit", new=True)
+    @patch("streamlit.elements.utils.get_session_state")
+    def test_no_warning_with_value_set_in_state(self, patched_get_session_state):
+        mock_session_state = MagicMock()
+        mock_session_state.is_new_state_value.return_value = True
+        patched_get_session_state.return_value = mock_session_state
+
+        st.number_input("the label", min_value=1, max_value=10, key="number_input")
+
+        c = self.get_delta_from_queue().new_element.number_input
+        self.assertEqual(c.label, "the label")
+        self.assertEqual(c.default, 1)
+
+        # Assert that no warning delta is enqueued when setting the widget
+        # value via st.session_state.
+        self.assertEqual(len(self.get_all_deltas_from_queue()), 1)