Sfoglia il codice sorgente

improve client state (#4597)

* improve client state

* no comma

* update python for unit tests

* overwrite it for windows

* bump other python versions
Khaleel Al-Adhami 4 mesi fa
parent
commit
880975ae94

+ 21 - 21
.github/workflows/benchmarks.yml

@@ -5,7 +5,7 @@ on:
     types:
     types:
       - closed
       - closed
     paths-ignore:
     paths-ignore:
-      - '**/*.md'
+      - "**/*.md"
 
 
 permissions:
 permissions:
   contents: read
   contents: read
@@ -15,21 +15,21 @@ defaults:
     shell: bash
     shell: bash
 
 
 env:
 env:
-  PYTHONIOENCODING: 'utf8'
+  PYTHONIOENCODING: "utf8"
   TELEMETRY_ENABLED: false
   TELEMETRY_ENABLED: false
-  NODE_OPTIONS: '--max_old_space_size=8192'
+  NODE_OPTIONS: "--max_old_space_size=8192"
   PR_TITLE: ${{ github.event.pull_request.title }}
   PR_TITLE: ${{ github.event.pull_request.title }}
 
 
 jobs:
 jobs:
   reflex-web:
   reflex-web:
-#    if: github.event.pull_request.merged == true
+    #    if: github.event.pull_request.merged == true
     strategy:
     strategy:
       fail-fast: false
       fail-fast: false
       matrix:
       matrix:
         # Show OS combos first in GUI
         # Show OS combos first in GUI
         os: [ubuntu-latest]
         os: [ubuntu-latest]
-        python-version: ['3.11.4']
-        node-version: ['18.x']
+        python-version: ["3.12.8"]
+        node-version: ["18.x"]
 
 
     runs-on: ${{ matrix.os }}
     runs-on: ${{ matrix.os }}
     steps:
     steps:
@@ -81,24 +81,24 @@ jobs:
       matrix:
       matrix:
         # Show OS combos first in GUI
         # Show OS combos first in GUI
         os: [ubuntu-latest, windows-latest, macos-latest]
         os: [ubuntu-latest, windows-latest, macos-latest]
-        python-version: ['3.9.18', '3.10.13', '3.11.5', '3.12.0']
+        python-version: ["3.9.21", "3.10.16", "3.11.11", "3.12.8"]
         exclude:
         exclude:
           - os: windows-latest
           - os: windows-latest
-            python-version: '3.10.13'
+            python-version: "3.10.16"
           - os: windows-latest
           - os: windows-latest
-            python-version: '3.9.18'
+            python-version: "3.9.21"
           # keep only one python version for MacOS
           # keep only one python version for MacOS
           - os: macos-latest
           - os: macos-latest
-            python-version: '3.9.18'
+            python-version: "3.9.21"
           - os: macos-latest
           - os: macos-latest
-            python-version: '3.10.13'
+            python-version: "3.10.16"
           - os: macos-latest
           - os: macos-latest
-            python-version: '3.12.0'
+            python-version: "3.11.11"
         include:
         include:
           - os: windows-latest
           - os: windows-latest
-            python-version: '3.10.11'
+            python-version: "3.10.11"
           - os: windows-latest
           - os: windows-latest
-            python-version: '3.9.13'
+            python-version: "3.9.13"
 
 
     runs-on: ${{ matrix.os }}
     runs-on: ${{ matrix.os }}
     steps:
     steps:
@@ -123,7 +123,7 @@ jobs:
           --event-type "${{ github.event_name }}" --pr-id "${{ github.event.pull_request.id }}"
           --event-type "${{ github.event_name }}" --pr-id "${{ github.event.pull_request.id }}"
 
 
   reflex-dist-size: # This job is used to calculate the size of the Reflex distribution (wheel file)
   reflex-dist-size: # This job is used to calculate the size of the Reflex distribution (wheel file)
-    if: github.event.pull_request.merged == true  
+    if: github.event.pull_request.merged == true
     timeout-minutes: 30
     timeout-minutes: 30
     strategy:
     strategy:
       # Prioritize getting more information out of the workflow (even if something fails)
       # Prioritize getting more information out of the workflow (even if something fails)
@@ -133,7 +133,7 @@ jobs:
       - uses: actions/checkout@v4
       - uses: actions/checkout@v4
       - uses: ./.github/actions/setup_build_env
       - uses: ./.github/actions/setup_build_env
         with:
         with:
-          python-version: 3.11.5
+          python-version: 3.12.8
           run-poetry-install: true
           run-poetry-install: true
           create-venv-at-path: .venv
           create-venv-at-path: .venv
       - name: Build reflex
       - name: Build reflex
@@ -143,12 +143,12 @@ jobs:
         # Only run if the database creds are available in this context.
         # Only run if the database creds are available in this context.
         run:
         run:
           poetry run python benchmarks/benchmark_package_size.py --os ubuntu-latest
           poetry run python benchmarks/benchmark_package_size.py --os ubuntu-latest
-          --python-version 3.11.5 --commit-sha "${{ github.sha }}" --pr-id "${{ github.event.pull_request.id }}"
+          --python-version 3.12.8 --commit-sha "${{ github.sha }}" --pr-id "${{ github.event.pull_request.id }}"
           --branch-name "${{ github.head_ref || github.ref_name }}"
           --branch-name "${{ github.head_ref || github.ref_name }}"
           --path ./dist
           --path ./dist
 
 
   reflex-venv-size: # This job calculates the total size of Reflex and its dependencies
   reflex-venv-size: # This job calculates the total size of Reflex and its dependencies
-    if: github.event.pull_request.merged == true  
+    if: github.event.pull_request.merged == true
     timeout-minutes: 30
     timeout-minutes: 30
     strategy:
     strategy:
       # Prioritize getting more information out of the workflow (even if something fails)
       # Prioritize getting more information out of the workflow (even if something fails)
@@ -156,7 +156,7 @@ jobs:
       matrix:
       matrix:
         # Show OS combos first in GUI
         # Show OS combos first in GUI
         os: [ubuntu-latest, windows-latest, macos-latest]
         os: [ubuntu-latest, windows-latest, macos-latest]
-        python-version: ['3.11.5']
+        python-version: ["3.12.8"]
 
 
     runs-on: ${{ matrix.os }}
     runs-on: ${{ matrix.os }}
     steps:
     steps:
@@ -186,6 +186,6 @@ jobs:
         run:
         run:
           poetry run python benchmarks/benchmark_package_size.py --os "${{ matrix.os }}"
           poetry run python benchmarks/benchmark_package_size.py --os "${{ matrix.os }}"
           --python-version "${{ matrix.python-version }}" --commit-sha "${{ github.sha }}"
           --python-version "${{ matrix.python-version }}" --commit-sha "${{ github.sha }}"
-          --pr-id "${{ github.event.pull_request.id }}" 
+          --pr-id "${{ github.event.pull_request.id }}"
           --branch-name "${{ github.head_ref || github.ref_name }}"
           --branch-name "${{ github.head_ref || github.ref_name }}"
-          --path ./.venv
+          --path ./.venv

+ 5 - 5
.github/workflows/check_generated_pyi.yml

@@ -6,16 +6,16 @@ concurrency:
 
 
 on:
 on:
   push:
   push:
-    branches: ['main']
+    branches: ["main"]
     # We don't just trigger on make_pyi.py and the components dir, because
     # We don't just trigger on make_pyi.py and the components dir, because
     # there are other things that can change the generator output
     # there are other things that can change the generator output
     # e.g. black version, reflex.Component, reflex.Var.
     # e.g. black version, reflex.Component, reflex.Var.
     paths-ignore:
     paths-ignore:
-      - '**/*.md'
+      - "**/*.md"
   pull_request:
   pull_request:
-    branches: ['main']
+    branches: ["main"]
     paths-ignore:
     paths-ignore:
-      - '**/*.md'
+      - "**/*.md"
 
 
 jobs:
 jobs:
   check-generated-pyi-components:
   check-generated-pyi-components:
@@ -25,7 +25,7 @@ jobs:
       - uses: actions/checkout@v4
       - uses: actions/checkout@v4
       - uses: ./.github/actions/setup_build_env
       - uses: ./.github/actions/setup_build_env
         with:
         with:
-          python-version: '3.11.5'
+          python-version: "3.12.8"
           run-poetry-install: true
           run-poetry-install: true
           create-venv-at-path: .venv
           create-venv-at-path: .venv
       - run: |
       - run: |

+ 32 - 35
.github/workflows/check_node_latest.yml

@@ -1,43 +1,40 @@
 name: integration-node-latest
 name: integration-node-latest
 
 
 on:
 on:
-    push:
-        branches:
-            - main
-    pull_request:
-        branches:
-            - main
+  push:
+    branches:
+      - main
+  pull_request:
+    branches:
+      - main
 
 
 env:
 env:
-    TELEMETRY_ENABLED: false
-    REFLEX_USE_SYSTEM_NODE: true
+  TELEMETRY_ENABLED: false
+  REFLEX_USE_SYSTEM_NODE: true
 
 
 jobs:
 jobs:
-    check_latest_node:
-        runs-on: ubuntu-22.04
-        strategy:
-            matrix:
-                python-version: ['3.12']
-                split_index: [1, 2]
-                node-version: ['node']
-            fail-fast: false
-
-        steps:
-            - uses: actions/checkout@v4
-            - uses: ./.github/actions/setup_build_env
-              with:
-                python-version: ${{ matrix.python-version }}
-                run-poetry-install: true
-                create-venv-at-path: .venv
-            - uses: actions/setup-node@v4
-              with:
-                node-version: ${{ matrix.node-version }}
-            - run: |
-                poetry run uv pip install pyvirtualdisplay pillow pytest-split
-                poetry run playwright install --with-deps
-            - run: |
-                poetry run pytest tests/test_node_version.py
-                poetry run pytest tests/integration --splits 2 --group ${{matrix.split_index}}
-  
-
+  check_latest_node:
+    runs-on: ubuntu-22.04
+    strategy:
+      matrix:
+        python-version: ["3.12.8"]
+        split_index: [1, 2]
+        node-version: ["node"]
+      fail-fast: false
 
 
+    steps:
+      - uses: actions/checkout@v4
+      - uses: ./.github/actions/setup_build_env
+        with:
+          python-version: ${{ matrix.python-version }}
+          run-poetry-install: true
+          create-venv-at-path: .venv
+      - uses: actions/setup-node@v4
+        with:
+          node-version: ${{ matrix.node-version }}
+      - run: |
+          poetry run uv pip install pyvirtualdisplay pillow pytest-split
+          poetry run playwright install --with-deps
+      - run: |
+          poetry run pytest tests/test_node_version.py
+          poetry run pytest tests/integration --splits 2 --group ${{matrix.split_index}}

+ 64 - 66
.github/workflows/check_outdated_dependencies.yml

@@ -1,88 +1,86 @@
 name: check-outdated-dependencies
 name: check-outdated-dependencies
 
 
 on:
 on:
-  push:  # This will trigger the action when a pull request is opened or updated.
+  push: # This will trigger the action when a pull request is opened or updated.
     branches:
     branches:
-      - 'release/**'  # This will trigger the action when any branch starting with "release/" is created.
-  workflow_dispatch:  # Allow manual triggering if needed.
+      - "release/**" # This will trigger the action when any branch starting with "release/" is created.
+  workflow_dispatch: # Allow manual triggering if needed.
 
 
 jobs:
 jobs:
   backend:
   backend:
     runs-on: ubuntu-latest
     runs-on: ubuntu-latest
 
 
     steps:
     steps:
-    - name: Checkout code
-      uses: actions/checkout@v3
+      - name: Checkout code
+        uses: actions/checkout@v3
 
 
-    - uses: ./.github/actions/setup_build_env
-      with:
-        python-version: '3.9'
-        run-poetry-install: true
-        create-venv-at-path: .venv
+      - uses: ./.github/actions/setup_build_env
+        with:
+          python-version: "3.9.21"
+          run-poetry-install: true
+          create-venv-at-path: .venv
 
 
-    - name: Check outdated backend dependencies
-      run: |
-        outdated=$(poetry show -oT)
-        echo "Outdated:"
-        echo "$outdated"
+      - name: Check outdated backend dependencies
+        run: |
+          outdated=$(poetry show -oT)
+          echo "Outdated:"
+          echo "$outdated"
 
 
-        filtered_outdated=$(echo "$outdated" | grep -vE 'pyright|ruff' || true)
-
-        if [ ! -z "$filtered_outdated" ]; then
-          echo "Outdated dependencies found:"
-          echo "$filtered_outdated"
-          exit 1
-        else
-          echo "All dependencies are up to date. (pyright and ruff are ignored)"
-        fi
+          filtered_outdated=$(echo "$outdated" | grep -vE 'pyright|ruff' || true)
 
 
+          if [ ! -z "$filtered_outdated" ]; then
+            echo "Outdated dependencies found:"
+            echo "$filtered_outdated"
+            exit 1
+          else
+            echo "All dependencies are up to date. (pyright and ruff are ignored)"
+          fi
 
 
   frontend:
   frontend:
     runs-on: ubuntu-latest
     runs-on: ubuntu-latest
 
 
     steps:
     steps:
-    - name: Checkout code
-      uses: actions/checkout@v4  
-    - uses: ./.github/actions/setup_build_env
-      with:
-        python-version: '3.10.11'
-        run-poetry-install: true
-        create-venv-at-path: .venv
-    - name: Clone Reflex Website Repo
-      uses: actions/checkout@v4
-      with:
-        repository: reflex-dev/reflex-web
-        ref: main
-        path: reflex-web
-    - name: Install Requirements for reflex-web
-      working-directory: ./reflex-web
-      run: poetry run uv pip install -r requirements.txt
-    - name: Install additional dependencies for DB access
-      run: poetry run uv pip install psycopg
-    - name: Init Website for reflex-web
-      working-directory: ./reflex-web
-      run: poetry run reflex init
-    - name: Run Website and Check for errors
-      run: |
-        poetry run bash scripts/integration.sh ./reflex-web dev
-    - name: Check outdated frontend dependencies
-      working-directory: ./reflex-web/.web
-      run: |
-        raw_outdated=$(/home/runner/.local/share/reflex/bun/bin/bun outdated)
-        outdated=$(echo "$raw_outdated" | grep -vE '\|\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\|' || true)
-        echo "Outdated:"
-        echo "$outdated"
-
-        # Ignore 3rd party dependencies that are not updated.
-        filtered_outdated=$(echo "$outdated" | grep -vE 'Package|@chakra-ui|lucide-react|@splinetool/runtime|ag-grid-react|framer-motion|react-markdown|remark-math|remark-gfm|rehype-katex|rehype-raw|remark-unwrap-images' || true)
-        no_extra=$(echo "$filtered_outdated" | grep -vE '\|\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-' || true)
+      - name: Checkout code
+        uses: actions/checkout@v4
+      - uses: ./.github/actions/setup_build_env
+        with:
+          python-version: "3.10.16"
+          run-poetry-install: true
+          create-venv-at-path: .venv
+      - name: Clone Reflex Website Repo
+        uses: actions/checkout@v4
+        with:
+          repository: reflex-dev/reflex-web
+          ref: main
+          path: reflex-web
+      - name: Install Requirements for reflex-web
+        working-directory: ./reflex-web
+        run: poetry run uv pip install -r requirements.txt
+      - name: Install additional dependencies for DB access
+        run: poetry run uv pip install psycopg
+      - name: Init Website for reflex-web
+        working-directory: ./reflex-web
+        run: poetry run reflex init
+      - name: Run Website and Check for errors
+        run: |
+          poetry run bash scripts/integration.sh ./reflex-web dev
+      - name: Check outdated frontend dependencies
+        working-directory: ./reflex-web/.web
+        run: |
+          raw_outdated=$(/home/runner/.local/share/reflex/bun/bin/bun outdated)
+          outdated=$(echo "$raw_outdated" | grep -vE '\|\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\|' || true)
+          echo "Outdated:"
+          echo "$outdated"
 
 
+          # Ignore 3rd party dependencies that are not updated.
+          filtered_outdated=$(echo "$outdated" | grep -vE 'Package|@chakra-ui|lucide-react|@splinetool/runtime|ag-grid-react|framer-motion|react-markdown|remark-math|remark-gfm|rehype-katex|rehype-raw|remark-unwrap-images' || true)
+          no_extra=$(echo "$filtered_outdated" | grep -vE '\|\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-' || true)
 
 
-        if [ ! -z "$no_extra" ]; then
-          echo "Outdated dependencies found:"
-          echo "$filtered_outdated"
-          exit 1
-        else
-          echo "All dependencies are up to date. (3rd party packages are ignored)"
-        fi
 
 
+          if [ ! -z "$no_extra" ]; then
+            echo "Outdated dependencies found:"
+            echo "$filtered_outdated"
+            exit 1
+          else
+            echo "All dependencies are up to date. (3rd party packages are ignored)"
+          fi

+ 2 - 2
.github/workflows/integration_app_harness.yml

@@ -22,8 +22,8 @@ jobs:
     timeout-minutes: 30
     timeout-minutes: 30
     strategy:
     strategy:
       matrix:
       matrix:
-        state_manager: ['redis', 'memory']
-        python-version: ['3.11.5', '3.12.0', '3.13.0']
+        state_manager: ["redis", "memory"]
+        python-version: ["3.11.11", "3.12.8", "3.13.1"]
         split_index: [1, 2]
         split_index: [1, 2]
       fail-fast: false
       fail-fast: false
     runs-on: ubuntu-22.04
     runs-on: ubuntu-22.04

+ 20 - 19
.github/workflows/integration_tests.yml

@@ -2,13 +2,13 @@ name: integration-tests
 
 
 on:
 on:
   push:
   push:
-    branches: ['main']
+    branches: ["main"]
     paths-ignore:
     paths-ignore:
-      - '**/*.md'
+      - "**/*.md"
   pull_request:
   pull_request:
-    branches: ['main']
+    branches: ["main"]
     paths-ignore:
     paths-ignore:
-      - '**/*.md'
+      - "**/*.md"
 
 
 concurrency:
 concurrency:
   group: ${{ github.workflow }}-${{ github.event.pull_request.id }}
   group: ${{ github.workflow }}-${{ github.event.pull_request.id }}
@@ -27,9 +27,9 @@ env:
   # TODO: can we fix windows encoding natively within reflex? Bug above can hit real users too (less common, but possible)
   # TODO: can we fix windows encoding natively within reflex? Bug above can hit real users too (less common, but possible)
   # - Catch encoding errors when printing logs
   # - Catch encoding errors when printing logs
   # - Best effort print lines that contain illegal chars (map to some default char, etc.)
   # - Best effort print lines that contain illegal chars (map to some default char, etc.)
-  PYTHONIOENCODING: 'utf8'
+  PYTHONIOENCODING: "utf8"
   TELEMETRY_ENABLED: false
   TELEMETRY_ENABLED: false
-  NODE_OPTIONS: '--max_old_space_size=8192'
+  NODE_OPTIONS: "--max_old_space_size=8192"
   PR_TITLE: ${{ github.event.pull_request.title }}
   PR_TITLE: ${{ github.event.pull_request.title }}
 
 
 jobs:
 jobs:
@@ -43,17 +43,22 @@ jobs:
       matrix:
       matrix:
         # Show OS combos first in GUI
         # Show OS combos first in GUI
         os: [ubuntu-latest, windows-latest]
         os: [ubuntu-latest, windows-latest]
-        python-version: ['3.9.18', '3.10.13', '3.11.5', '3.12.0', '3.13.0']
+        python-version: ["3.9.21", "3.10.16", "3.11.11", "3.12.8", "3.13.1"]
+        # Windows is a bit behind on Python version availability in Github
         exclude:
         exclude:
           - os: windows-latest
           - os: windows-latest
-            python-version: '3.10.13'
+            python-version: "3.11.11"
           - os: windows-latest
           - os: windows-latest
-            python-version: '3.9.18'
+            python-version: "3.10.16"
+          - os: windows-latest
+            python-version: "3.9.21"
         include:
         include:
           - os: windows-latest
           - os: windows-latest
-            python-version: '3.10.11'
+            python-version: "3.11.9"
+          - os: windows-latest
+            python-version: "3.10.11"
           - os: windows-latest
           - os: windows-latest
-            python-version: '3.9.13'
+            python-version: "3.9.13"
 
 
     runs-on: ${{ matrix.os }}
     runs-on: ${{ matrix.os }}
     steps:
     steps:
@@ -117,18 +122,16 @@ jobs:
           --branch-name "${{ github.head_ref || github.ref_name }}" --pr-id "${{ github.event.pull_request.id }}"
           --branch-name "${{ github.head_ref || github.ref_name }}" --pr-id "${{ github.event.pull_request.id }}"
           --app-name "counter"
           --app-name "counter"
 
 
-
-
   reflex-web:
   reflex-web:
     strategy:
     strategy:
       fail-fast: false
       fail-fast: false
       matrix:
       matrix:
         # Show OS combos first in GUI
         # Show OS combos first in GUI
         os: [ubuntu-latest]
         os: [ubuntu-latest]
-        python-version: ['3.10.11', '3.11.4']
+        python-version: ["3.11.11", "3.12.8"]
 
 
     env:
     env:
-      REFLEX_WEB_WINDOWS_OVERRIDE: '1'
+      REFLEX_WEB_WINDOWS_OVERRIDE: "1"
     runs-on: ${{ matrix.os }}
     runs-on: ${{ matrix.os }}
     steps:
     steps:
       - uses: actions/checkout@v4
       - uses: actions/checkout@v4
@@ -173,7 +176,7 @@ jobs:
       - uses: actions/checkout@v4
       - uses: actions/checkout@v4
       - uses: ./.github/actions/setup_build_env
       - uses: ./.github/actions/setup_build_env
         with:
         with:
-          python-version: '3.11.4'
+          python-version: "3.11.11"
           run-poetry-install: true
           run-poetry-install: true
           create-venv-at-path: .venv
           create-venv-at-path: .venv
       - name: Create app directory
       - name: Create app directory
@@ -192,14 +195,13 @@ jobs:
           # Check that npm is home
           # Check that npm is home
           npm -v
           npm -v
           poetry run bash scripts/integration.sh ./rx-shout-from-template prod
           poetry run bash scripts/integration.sh ./rx-shout-from-template prod
-  
 
 
   reflex-web-macos:
   reflex-web-macos:
     if: github.event_name == 'push' && github.ref == 'refs/heads/main'
     if: github.event_name == 'push' && github.ref == 'refs/heads/main'
     strategy:
     strategy:
       fail-fast: false
       fail-fast: false
       matrix:
       matrix:
-        python-version: ['3.11.5', '3.12.0']
+        python-version: ["3.11.11", "3.12.8"]
     runs-on: macos-latest
     runs-on: macos-latest
     steps:
     steps:
       - uses: actions/checkout@v4
       - uses: actions/checkout@v4
@@ -233,4 +235,3 @@ jobs:
           --python-version "${{ matrix.python-version }}" --commit-sha "${{ github.sha }}"
           --python-version "${{ matrix.python-version }}" --commit-sha "${{ github.sha }}"
           --pr-id "${{ github.event.pull_request.id }}" --branch-name "${{ github.head_ref || github.ref_name }}"
           --pr-id "${{ github.event.pull_request.id }}" --branch-name "${{ github.head_ref || github.ref_name }}"
           --app-name "reflex-web" --path ./reflex-web/.web
           --app-name "reflex-web" --path ./reflex-web/.web
-  

+ 3 - 3
.github/workflows/pre-commit.yml

@@ -6,12 +6,12 @@ concurrency:
 
 
 on:
 on:
   pull_request:
   pull_request:
-    branches: ['main']
+    branches: ["main"]
   push:
   push:
     # Note even though this job is called "pre-commit" and runs "pre-commit", this job will run
     # Note even though this job is called "pre-commit" and runs "pre-commit", this job will run
     # also POST-commit on main also!  In case there are mishandled merge conflicts / bad auto-resolves
     # also POST-commit on main also!  In case there are mishandled merge conflicts / bad auto-resolves
     # when merging into main branch.
     # when merging into main branch.
-    branches: ['main']
+    branches: ["main"]
 
 
 jobs:
 jobs:
   pre-commit:
   pre-commit:
@@ -23,7 +23,7 @@ jobs:
         with:
         with:
           # running vs. one version of Python is OK
           # running vs. one version of Python is OK
           # i.e. ruff, black, etc.
           # i.e. ruff, black, etc.
-          python-version: 3.11.5
+          python-version: 3.12.8
           run-poetry-install: true
           run-poetry-install: true
           create-venv-at-path: .venv
           create-venv-at-path: .venv
       # TODO pre-commit related stuff can be cached too (not a bottleneck yet)
       # TODO pre-commit related stuff can be cached too (not a bottleneck yet)

+ 15 - 11
.github/workflows/unit_tests.yml

@@ -6,13 +6,13 @@ concurrency:
 
 
 on:
 on:
   push:
   push:
-    branches: ['main']
+    branches: ["main"]
     paths-ignore:
     paths-ignore:
-      - '**/*.md'
+      - "**/*.md"
   pull_request:
   pull_request:
-    branches: ['main']
+    branches: ["main"]
     paths-ignore:
     paths-ignore:
-      - '**/*.md'
+      - "**/*.md"
 
 
 permissions:
 permissions:
   contents: read
   contents: read
@@ -28,18 +28,22 @@ jobs:
       fail-fast: false
       fail-fast: false
       matrix:
       matrix:
         os: [ubuntu-latest, windows-latest]
         os: [ubuntu-latest, windows-latest]
-        python-version: ['3.9.18', '3.10.13', '3.11.5', '3.12.0', '3.13.0']
+        python-version: ["3.9.21", "3.10.16", "3.11.11", "3.12.8", "3.13.1"]
         # Windows is a bit behind on Python version availability in Github
         # Windows is a bit behind on Python version availability in Github
         exclude:
         exclude:
           - os: windows-latest
           - os: windows-latest
-            python-version: '3.10.13'
+            python-version: "3.11.11"
           - os: windows-latest
           - os: windows-latest
-            python-version: '3.9.18'
+            python-version: "3.10.16"
+          - os: windows-latest
+            python-version: "3.9.21"
         include:
         include:
           - os: windows-latest
           - os: windows-latest
-            python-version: '3.10.11'
+            python-version: "3.11.9"
+          - os: windows-latest
+            python-version: "3.10.11"
           - os: windows-latest
           - os: windows-latest
-            python-version: '3.9.13'
+            python-version: "3.9.13"
     runs-on: ${{ matrix.os }}
     runs-on: ${{ matrix.os }}
 
 
     # Service containers to run with `runner-job`
     # Service containers to run with `runner-job`
@@ -89,7 +93,7 @@ jobs:
       fail-fast: false
       fail-fast: false
       matrix:
       matrix:
         # Note: py39, py310 versions chosen due to available arm64 darwin builds.
         # Note: py39, py310 versions chosen due to available arm64 darwin builds.
-        python-version: ['3.9.13', '3.10.11', '3.11.5', '3.12.0', '3.13.0']
+        python-version: ["3.9.21", "3.10.16", "3.11.11", "3.12.8", "3.13.1"]
     runs-on: macos-latest
     runs-on: macos-latest
     steps:
     steps:
       - uses: actions/checkout@v4
       - uses: actions/checkout@v4
@@ -106,4 +110,4 @@ jobs:
         run: |
         run: |
           export PYTHONUNBUFFERED=1
           export PYTHONUNBUFFERED=1
           poetry run uv pip install "pydantic~=1.10"
           poetry run uv pip install "pydantic~=1.10"
-          poetry run pytest tests/units --cov --no-cov-on-fail --cov-report=
+          poetry run pytest tests/units --cov --no-cov-on-fail --cov-report=

+ 41 - 19
reflex/experimental/client_state.py

@@ -12,7 +12,7 @@ from reflex.event import EventChain, EventHandler, EventSpec, run_script
 from reflex.utils.imports import ImportVar
 from reflex.utils.imports import ImportVar
 from reflex.vars import VarData, get_unique_variable_name
 from reflex.vars import VarData, get_unique_variable_name
 from reflex.vars.base import LiteralVar, Var
 from reflex.vars.base import LiteralVar, Var
-from reflex.vars.function import FunctionVar
+from reflex.vars.function import ArgsFunctionOperationBuilder, FunctionVar
 
 
 NoValue = object()
 NoValue = object()
 
 
@@ -45,6 +45,7 @@ class ClientStateVar(Var):
     # Track the names of the getters and setters
     # Track the names of the getters and setters
     _setter_name: str = dataclasses.field(default="")
     _setter_name: str = dataclasses.field(default="")
     _getter_name: str = dataclasses.field(default="")
     _getter_name: str = dataclasses.field(default="")
+    _id_name: str = dataclasses.field(default="")
 
 
     # Whether to add the var and setter to the global `refs` object for use in any Component.
     # Whether to add the var and setter to the global `refs` object for use in any Component.
     _global_ref: bool = dataclasses.field(default=True)
     _global_ref: bool = dataclasses.field(default=True)
@@ -96,6 +97,7 @@ class ClientStateVar(Var):
         """
         """
         if var_name is None:
         if var_name is None:
             var_name = get_unique_variable_name()
             var_name = get_unique_variable_name()
+        id_name = "id_" + get_unique_variable_name()
         if not isinstance(var_name, str):
         if not isinstance(var_name, str):
             raise ValueError("var_name must be a string.")
             raise ValueError("var_name must be a string.")
         if default is NoValue:
         if default is NoValue:
@@ -106,19 +108,23 @@ class ClientStateVar(Var):
             default_var = default
             default_var = default
         setter_name = f"set{var_name.capitalize()}"
         setter_name = f"set{var_name.capitalize()}"
         hooks: dict[str, VarData | None] = {
         hooks: dict[str, VarData | None] = {
+            f"const {id_name} = useId()": None,
             f"const [{var_name}, {setter_name}] = useState({default_var!s})": None,
             f"const [{var_name}, {setter_name}] = useState({default_var!s})": None,
         }
         }
         imports = {
         imports = {
-            "react": [ImportVar(tag="useState")],
+            "react": [ImportVar(tag="useState"), ImportVar(tag="useId")],
         }
         }
         if global_ref:
         if global_ref:
-            hooks[f"{_client_state_ref(var_name)} = {var_name}"] = None
-            hooks[f"{_client_state_ref(setter_name)} = {setter_name}"] = None
+            hooks[f"{_client_state_ref(var_name)} ??= {{}}"] = None
+            hooks[f"{_client_state_ref(setter_name)} ??= {{}}"] = None
+            hooks[f"{_client_state_ref(var_name)}[{id_name}] = {var_name}"] = None
+            hooks[f"{_client_state_ref(setter_name)}[{id_name}] = {setter_name}"] = None
             imports.update(_refs_import)
             imports.update(_refs_import)
         return cls(
         return cls(
             _js_expr="",
             _js_expr="",
             _setter_name=setter_name,
             _setter_name=setter_name,
             _getter_name=var_name,
             _getter_name=var_name,
+            _id_name=id_name,
             _global_ref=global_ref,
             _global_ref=global_ref,
             _var_type=default_var._var_type,
             _var_type=default_var._var_type,
             _var_data=VarData.merge(
             _var_data=VarData.merge(
@@ -144,10 +150,11 @@ class ClientStateVar(Var):
         return (
         return (
             Var(
             Var(
                 _js_expr=(
                 _js_expr=(
-                    _client_state_ref(self._getter_name)
+                    _client_state_ref(self._getter_name) + f"[{self._id_name}]"
                     if self._global_ref
                     if self._global_ref
                     else self._getter_name
                     else self._getter_name
-                )
+                ),
+                _var_data=self._var_data,
             )
             )
             .to(self._var_type)
             .to(self._var_type)
             ._replace(
             ._replace(
@@ -170,28 +177,43 @@ class ClientStateVar(Var):
         Returns:
         Returns:
             A special EventChain Var which will set the value when triggered.
             A special EventChain Var which will set the value when triggered.
         """
         """
+        _var_data = VarData(imports=_refs_import if self._global_ref else {})
+
+        arg_name = get_unique_variable_name()
         setter = (
         setter = (
-            _client_state_ref(self._setter_name)
+            ArgsFunctionOperationBuilder.create(
+                args_names=(arg_name,),
+                return_expr=Var("Array.prototype.forEach.call")
+                .to(FunctionVar)
+                .call(
+                    Var("Object.values")
+                    .to(FunctionVar)
+                    .call(Var(_client_state_ref(self._setter_name))),
+                    ArgsFunctionOperationBuilder.create(
+                        args_names=("setter",),
+                        return_expr=Var("setter").to(FunctionVar).call(Var(arg_name)),
+                    ),
+                ),
+                _var_data=_var_data,
+            )
             if self._global_ref
             if self._global_ref
-            else self._setter_name
+            else Var(self._setter_name, _var_data=_var_data).to(FunctionVar)
         )
         )
-        _var_data = VarData(imports=_refs_import if self._global_ref else {})
+
         if value is not NoValue:
         if value is not NoValue:
             # This is a hack to make it work like an EventSpec taking an arg
             # This is a hack to make it work like an EventSpec taking an arg
             value_var = LiteralVar.create(value)
             value_var = LiteralVar.create(value)
-            _var_data = VarData.merge(_var_data, value_var._get_all_var_data())
             value_str = str(value_var)
             value_str = str(value_var)
 
 
-            if value_str.startswith("_"):
+            setter = ArgsFunctionOperationBuilder.create(
                 # remove patterns of ["*"] from the value_str using regex
                 # remove patterns of ["*"] from the value_str using regex
-                arg = re.sub(r"\[\".*\"\]", "", value_str)
-                setter = f"(({arg}) => {setter}({value_str}))"
-            else:
-                setter = f"(() => {setter}({value_str}))"
-        return Var(
-            _js_expr=setter,
-            _var_data=_var_data,
-        ).to(FunctionVar, EventChain)
+                args_names=(re.sub(r"\[\".*\"\]", "", value_str),)
+                if value_str.startswith("_")
+                else (),
+                return_expr=setter.call(value_var),
+            )
+
+        return setter.to(FunctionVar, EventChain)
 
 
     @property
     @property
     def set(self) -> Var:
     def set(self) -> Var: