Repository: reflex-dev/reflex

Issue:
[REF-3016] Upload ID should allow characters
**Describe the bug**
Using an ID like this `on_click=State.handle_upload(rx.upload_files(upload_id="cv-upload")),`
Will cause a rendering like this: 
```python
{files:filesById.cv-upload,upload_id:`cv-upload`}
```

**Expected behavior**
Should be rendered as 
```python
{files:filesById[`cv-upload`],...
```
And not cause the error `upload is not defined`.

Can also be solved by throwing an error and editing the docs, but that would not be the preferred solution.

<sub>[REF-3016](https://linear.app/reflex-dev/issue/REF-3016/upload-id-must-not-contain-or-other-special-characters)</sub>


Discussion:

Patch:
diff --git a/reflex/components/core/upload.py b/reflex/components/core/upload.py
index 1bcfa3b5461..d499772983b 100644
--- a/reflex/components/core/upload.py
+++ b/reflex/components/core/upload.py
@@ -50,10 +50,18 @@ def upload_file(id_: str = DEFAULT_UPLOAD_ID) -> BaseVar:
     Returns:
         A var referencing the file upload drop trigger.
     """
+    id_var = Var.create_safe(id_, _var_is_string=True)
+    var_name = f"""e => setFilesById(filesById => {{
+    const updatedFilesById = Object.assign({{}}, filesById);
+    updatedFilesById[{id_var._var_name_unwrapped}] = e;
+    return updatedFilesById;
+  }})
+    """
+
     return BaseVar(
-        _var_name=f"e => setFilesById(filesById => ({{...filesById, {id_}: e}}))",
+        _var_name=var_name,
         _var_type=EventChain,
-        _var_data=upload_files_context_var_data,
+        _var_data=VarData.merge(upload_files_context_var_data, id_var._var_data),
     )
 
 
@@ -67,10 +75,11 @@ def selected_files(id_: str = DEFAULT_UPLOAD_ID) -> BaseVar:
     Returns:
         A var referencing the list of selected file paths.
     """
+    id_var = Var.create_safe(id_, _var_is_string=True)
     return BaseVar(
-        _var_name=f"(filesById.{id_} ? filesById.{id_}.map((f) => (f.path || f.name)) : [])",
+        _var_name=f"(filesById[{id_var._var_name_unwrapped}] ? filesById[{id_var._var_name_unwrapped}].map((f) => (f.path || f.name)) : [])",
         _var_type=List[str],
-        _var_data=upload_files_context_var_data,
+        _var_data=VarData.merge(upload_files_context_var_data, id_var._var_data),
     )
 
 
@@ -99,7 +108,9 @@ def cancel_upload(upload_id: str) -> EventSpec:
     Returns:
         An event spec that cancels the upload when triggered.
     """
-    return call_script(f"upload_controllers[{upload_id!r}]?.abort()")
+    return call_script(
+        f"upload_controllers[{Var.create_safe(upload_id, _var_is_string=True)._var_name_unwrapped!r}]?.abort()"
+    )
 
 
 def get_upload_dir() -> Path:
diff --git a/reflex/event.py b/reflex/event.py
index 5d2a32190fd..c9799a527dd 100644
--- a/reflex/event.py
+++ b/reflex/event.py
@@ -386,12 +386,12 @@ def as_event_spec(self, handler: EventHandler) -> EventSpec:
         )
 
         upload_id = self.upload_id or DEFAULT_UPLOAD_ID
-
         spec_args = [
             (
                 Var.create_safe("files", _var_is_string=False),
                 Var.create_safe(
-                    f"filesById.{upload_id}", _var_is_string=False
+                    f"filesById[{Var.create_safe(upload_id, _var_is_string=True)._var_name_unwrapped}]",
+                    _var_is_string=False,
                 )._replace(_var_data=upload_files_context_var_data),
             ),
             (


Test patch:
diff --git a/tests/components/forms/test_uploads.py b/tests/components/forms/test_uploads.py
index 9050bc0ddfa..f8856a20f7e 100644
--- a/tests/components/forms/test_uploads.py
+++ b/tests/components/forms/test_uploads.py
@@ -39,6 +39,19 @@ def upload_component():
     return upload_component()
 
 
+@pytest.fixture
+def upload_component_id_special():
+    def upload_component():
+        return rx.upload(
+            rx.button("select file"),
+            rx.text("Drag and drop files here or click to select files"),
+            border="1px dotted black",
+            id="#spec!`al-_98ID",
+        )
+
+    return upload_component()
+
+
 @pytest.fixture
 def upload_component_with_props():
     """A test upload component with props function.
@@ -72,7 +85,12 @@ def test_upload_root_component_render(upload_root_component):
     assert upload["props"] == [
         "id={`default`}",
         "multiple={true}",
-        "onDrop={e => setFilesById(filesById => ({...filesById, default: e}))}",
+        "onDrop={e => setFilesById(filesById => {\n"
+        "    const updatedFilesById = Object.assign({}, filesById);\n"
+        "    updatedFilesById[`default`] = e;\n"
+        "    return updatedFilesById;\n"
+        "  })\n"
+        "    }",
         "ref={ref_default}",
     ]
     assert upload["args"] == ("getRootProps", "getInputProps")
@@ -114,7 +132,12 @@ def test_upload_component_render(upload_component):
     assert upload["props"] == [
         "id={`default`}",
         "multiple={true}",
-        "onDrop={e => setFilesById(filesById => ({...filesById, default: e}))}",
+        "onDrop={e => setFilesById(filesById => {\n"
+        "    const updatedFilesById = Object.assign({}, filesById);\n"
+        "    updatedFilesById[`default`] = e;\n"
+        "    return updatedFilesById;\n"
+        "  })\n"
+        "    }",
         "ref={ref_default}",
     ]
     assert upload["args"] == ("getRootProps", "getInputProps")
@@ -156,6 +179,27 @@ def test_upload_component_with_props_render(upload_component_with_props):
         "maxFiles={2}",
         "multiple={true}",
         "noDrag={true}",
-        "onDrop={e => setFilesById(filesById => ({...filesById, default: e}))}",
+        "onDrop={e => setFilesById(filesById => {\n"
+        "    const updatedFilesById = Object.assign({}, filesById);\n"
+        "    updatedFilesById[`default`] = e;\n"
+        "    return updatedFilesById;\n"
+        "  })\n"
+        "    }",
         "ref={ref_default}",
     ]
+
+
+def test_upload_component_id_with_special_chars(upload_component_id_special):
+    upload = upload_component_id_special.render()
+
+    assert upload["props"] == [
+        r"id={`#spec!\`al-_98ID`}",
+        "multiple={true}",
+        "onDrop={e => setFilesById(filesById => {\n"
+        "    const updatedFilesById = Object.assign({}, filesById);\n"
+        "    updatedFilesById[`#spec!\\`al-_98ID`] = e;\n"
+        "    return updatedFilesById;\n"
+        "  })\n"
+        "    }",
+        "ref={ref__spec_al__98ID}",
+    ]