Skip to content

[LiveComponent] Advanced Polling Features #2965

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: 2.x
Choose a base branch
from

Conversation

xDeSwa
Copy link
Contributor

@xDeSwa xDeSwa commented Jul 30, 2025

Q A
Bug fix? no
New feature? yes
Docs? yes
Issues
License MIT

Added support for poll lifecycle hooks and pollingDirector control in UX LiveComponent

This PR introduces the following updates to the LiveComponent polling system:

  • Added limit(n) modifier to limit the number of poll iterations.
  • Added new lifecycle events: poll:started, poll:running, poll:paused, poll:stopped, and poll:error.
  • Added the pollingDirector API to start, stop, pause, or resume polling via JavaScript.
  • Added usage examples using Stimulus controllers.
  • Updated documentation with examples and descriptions for all new methods and hooks.

This enhancement introduces support for limiting polling cycles using the limit modifier. Once the limit is reached, the polling will automatically stop.

Additionally, new lifecycle hooks (poll:started, poll:running, poll:paused, poll:stopped, poll:error ) provide full control over the polling process via JavaScript (e.g. Stimulus controllers).

Developers can now track the polling count value to build dynamic UI components such as counters, countdown timers, or progress bars.

Preview:

aaa.mp4

Basic usage

By default, polling runs the $render action every 2000 milliseconds (2 seconds).


    {# Unlimited polling every 2 seconds (default) using $render #}
    <div {{ attributes }} data-poll>...</div>

    {# Poll every 5 seconds, up to 20 times, using $render #}
    <div {{ attributes }} data-poll="delay(5000)|limit(20)|$render">...</div>

    {# Poll a custom action (savePost) every 3 seconds, up to 10 times #}
    <div {{ attributes }} data-poll="delay(3000)|limit(10)|savePost">...</div>

Available Modifiers

  • delay(ms) — The delay between polls in milliseconds (default: 2000)
  • limit(n) — Maximum number of times to run the poll (default: unlimited)
  • actionName — The component action to call (default: $render)

Poll Hooks

The component emits lifecycle hooks during polling. You can listen to these using JavaScript, for example:

    // controllers/poll_controller.js
    import { Controller } from '@hotwired/stimulus';
    import { getComponent } from '@symfony/ux-live-component';

    export default class extends Controller {
        async connect() {
            this.component = await getComponent(this.element);

            // Disable default error window (optional)
            this.component.on('response:error', (backendResponse, controls) => {
                controls.displayError = false;
            });

            this.component.on('poll:started', ({ actionName, limit }) => {
                console.log(`Polling started: ${actionName}, limit: ${limit}`);
            });

            this.component.on('poll:running', ({ actionName, count, limit }) => {
                console.log(`Polling running: ${actionName} (${count}/${limit})`);
            });

            this.component.on('poll:paused', ({ actionName, count, limit }) => {
                console.log(`Polling paused: ${actionName}`);
            });

            this.component.on('poll:stopped', ({ actionName, finalCount, limit }) => {
                console.log(`Polling stopped: ${actionName}, total runs: ${finalCount}`);
            });

            this.component.on('poll:error', ({ actionName, finalCount, limit, errorMessage }) => {
                console.error(`Polling error on ${actionName}: ${errorMessage}`);
            });
        }
    }

These events are dispatched on the component and can be handled using Stimulus. You must retrieve the component instance with getComponent(this.element) before accessing event listeners.

Handling Poll Actions (Start, Stop, Pause, Resume)

Polling can be programmatically managed using the pollingDirector API.

This allows you to start, pause, resume, or stop polling dynamically for a given action.


    <div {{ attributes.defaults(stimulus_controller('poll')) }} data-poll="delay(5000)|limit(50)|$render">
        <button type="button" data-action="click->poll#start" data-poll-action-param="$render">Start</button>
        <button type="button" data-action="click->poll#stop" data-poll-action-param="$render">Stop</button>
    </div>
    // controllers/poll_controller.js
    import { Controller } from '@hotwired/stimulus';
    import { getComponent } from '@symfony/ux-live-component';

    export default class extends Controller {
        static values = {
            action: String
        }

        async start(event) {
            const actionName = event.params.action;
            (await getComponent(this.element)).pollingDirector.start(actionName);
        }

        async stop(event) {
            const actionName = event.params.action;
            (await getComponent(this.element)).pollingDirector.stop(actionName);
        }

        async pause(event) {
            const actionName = event.params.action;
            (await getComponent(this.element)).pollingDirector.pause(actionName);
        }

        async resume(event) {
            const actionName = event.params.action;
            (await getComponent(this.element)).pollingDirector.resume(actionName);
        }
    }

Available Methods

The pollingDirector API exposes the following methods:

  • component.pollingDirector.start(actionName) — Starts polling for the given action (if previously stopped).
  • component.pollingDirector.pause(actionName) — Temporarily pauses polling. Can be resumed later.
  • component.pollingDirector.resume(actionName) — Resumes a previously paused poll.
  • component.pollingDirector.stop(actionName) — Stops polling entirely. Use start() to restart.

@carsonbot carsonbot added Feature New Feature LiveComponent Status: Needs Review Needs to be reviewed labels Jul 30, 2025
Copy link
Contributor

📊 Packages dist files size difference

Thanks for the PR! Here is the difference in size of the packages dist files between the base branch and the PR.
Please review the changes and make sure they are expected.

FileBefore (Size / Gzip)After (Size / Gzip)
LiveComponent
live_controller.d.ts 7.96 kB / 1.96 kB 8.74 kB+10% 📈 / 2.08 kB+6% 📈
live_controller.js 99.04 kB / 21.35 kB 104.81 kB+6% 📈 / 22.49 kB+5% 📈

@smnandre
Copy link
Member

(won't have time to review attentively before next week, but this looks great!)

@@ -12,6 +12,9 @@ export default class implements PluginInterface {
this.pollingDirector = new PollingDirector(component);
this.initializePolling();

// access from stimulus_controller
(component as any).pollingDirector = this.pollingDirector;
Copy link
Member

Choose a reason for hiding this comment

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

Using any is a bad practice, if it's really impossible to type the property, then we must use unknown.

Here, we know that component is type of class Component that we define ourselves. So, you must add the property pollingDirector on Component class instead

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Feature New Feature LiveComponent Status: Needs Review Needs to be reviewed
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants