Skip to content

fix: use_effect() skipping cleanups and re-running setups before cleanups finish - #7005

Merged
FeodorFitsner merged 5 commits into
flet-1.1.0from
fix/use-effect-cleanup-follow-up
Oct 27, 2026
Merged

FeodorFitsner merged 5 commits into
flet-1.1.0from
fix/use-effect-cleanup-follow-up

Conversation

@ndonkoHenri

@ndonkoHenri ndonkoHenri commented Oct 26, 2026 •

Copy link
Copy Markdown
Contributor

Follow-up to #6983, which fixed #6963: a cleanup returned by a use_effect setup was lost once the component re-rendered.

What was still wrong

  • Passing cleanup= as well as returning a cleanup ran only one of them.
  • async cleanups never ran on a dependency change. The cleanup started a task, then the setup that followed cancelled the effect's tasks, including the cleanup it had just started.
  • Order wasn't guaranteed. An async cleanup could still be running when the setup ran again. For example, opening a new connection while the old one was still flushing and closing.

What this does

An effect's cleanups now always finish, in order, before its setup runs again.

  • Order: the cleanup the setup returned runs first, then cleanup=. Non-async cleanups run right away. Once an async one starts, the rest finish in a task.
  • Waiting: while that task runs, the effect's later setups and cleanups wait in a per-effect queue. The queue runs as soon as the task is done.
    • A setup that was superseded while waiting is skipped.
    • None runs after the component unmounts, or after the session closes or disconnects.
  • One setup per render: effect requests carry the setup function they were scheduled with, so each render's setup runs once, even when renders pile up quickly.
  • One cleanup per setup: cleanups run only if a setup ran since the last ones. A setup on an effect that's still active runs its cleanups first. A setup that runs after its component unmounted is cleaned up right away, which keeps on_unmounted working when a component unmounts before its effects ran.
  • Errors: one failing cleanup doesn't keep the others from running, and errors go to the session's error report. A CancelledError raised by an effect counts as cancelled, not as an error.
  • Garbage collection: cleanup tasks are held by the session until they finish, so they aren't collected while pending.

An async setup's return value still isn't used as a cleanup. The use_effect docstring says so, and points to cleanup= instead.

Docs

The use_effect docstring now explains the lifecycle, and that a returned cleanup sees its own setup's values while cleanup= is the latest render's function. It also gains an example that returns a cleanup. on_updated now says it also runs after mount, which it always did.

Tests

The effect tests move into tests/test_use_effect.py:

  • TestEffectScheduling checks what a component queues, with _schedule_effect stubbed.
  • TestEffectLoop drives effects end-to-end through a real Session effect loop.

The tests #6983 added in test_component_effects.py and test_session_disconnect_buffering.py are folded in or covered by the new ones. test_component_effects.py, left with a single unmount test, becomes test_component_unmount.py.

Testing

  • Python 3.13: test_use_effect.py (31 tests) passed over repeat runs with -W error. The full flet suite: 461 passed, 9 skipped.
  • Python 3.10: the effect test files pass. The Task.cancelling() test is skipped, since it needs 3.11.
  • Against the old code: the new tests for this behavior fail on flet-1.1.0.
  • In a real app: the docstring's clock example ran with flet run --web. It ticked, re-rendered every second, and ran its returned cleanup exactly once per unmount, over two show/hide cycles.

Summary by Sourcery

Make use_effect cleanup execution reliable by serializing cleanup and setup work across rerenders, unmounts, and asynchronous operations.

Bug Fixes:

  • Ensure use_effect cleanups are all executed exactly once and completed in order before an effect setup runs again or the component unmounts.
  • Prevent async cleanup tasks from being cancelled prematurely, while cancelling pending async setups on rerender or unmount.
  • Handle cleanup failures independently, report effect errors, and treat cancellation as non-error behavior.

Enhancements:

  • Preserve the setup function associated with each scheduled render, skip superseded setups, and prevent queued work from running after unmount, disconnect, or session closure.
  • Expand use_effect, on_mounted, on_unmounted, and on_updated documentation with lifecycle semantics and examples.

Tests:

  • Consolidate effect scheduling and end-to-end lifecycle coverage in test_use_effect.py, including ordering, cancellation, errors, queuing, unmounting, and garbage collection.
  • Separate component unmount cleanup coverage into test_component_unmount.py.

Chores:

  • Update the changelog for comprehensive use_effect cleanup handling.

…lled

When a setup returned a cleanup and `cleanup=` was passed too, only one of
them ran. The returned cleanup now runs first, then the argument, each
reported on its own if it raises so the other still runs.

A dependency change queued the cleanup and then the setup, and the setup
cancelled the effect's tasks, killing the async cleanup it had just
started. Only a still-running setup task is cancelled now.

The effect tests move into test_use_effect.py, grouped into scheduling and
end-to-end effect loop tests, and test_component_effects.py, left with an
unmount test, becomes test_component_unmount.py.
An effect's cleanups now always finish, in order, before its setup runs
again. Non-async cleanups run right away; once an async one starts, the
rest finish in a task, and the effect's later setups and cleanups wait in
a per-effect queue that runs as soon as the task is done. Superseded
setups are skipped, and none runs after the component unmounts or the
session closes or disconnects.

Effect requests carry the setup function they were scheduled with, so
each render's setup runs once. Cleanups run only if a setup ran since the
last ones, a setup on an active effect runs its cleanups first, and a
setup that runs after its component unmounted is cleaned up right away.

Cleanup tasks are held by the session until they finish, so they aren't
garbage collected while pending. A CancelledError raised by an effect
counts as cancelled rather than failed, and one failing cleanup doesn't
keep the others from running.
The use_effect docstring now explains when the setup and cleanups run,
that cleanups always finish in order before the setup runs again, and
that a returned cleanup sees its own setup's values while `cleanup` is
the latest render's function. It gains an example that returns a
cleanup.

on_updated says it also runs after the component mounts, and the
on_mounted, on_unmounted and on_updated docstrings drop the repeated
types and note that `fn` may be async.

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've reviewed your changes and they look great!


Sourcery is free for open source - if you like our reviews please consider sharing them ✨

@cloudflare-workers-and-pages

Copy link
Copy Markdown

Deploying flet-website-v2 with  Cloudflare Pages  Cloudflare Pages

Latest commit: 71c4510
Status: ✅  Deploy successful!
Preview URL: https://274698ab.flet-website-v2.pages.dev
Branch Preview URL: https://fix-use-effect-cleanup-follo.flet-website-v2.pages.dev

View logs

@FeodorFitsner
FeodorFitsner merged commit 86b96a2 into flet-1.1.0 Oct 27, 2026
1 of 92 checks passed
@FeodorFitsner
FeodorFitsner deleted the fix/use-effect-cleanup-follow-up branch October 27, 2026 19:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants