Skip to content

[Live] Add signature overload for on and off methods of component #1685

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

Merged
merged 1 commit into from
Apr 30, 2024

Conversation

YummYume
Copy link
Contributor

@YummYume YummYume commented Apr 5, 2024

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

Following some feedback in #1676. This PR only affects TypeScript typing.

Provides automatic typing for the on and off events of a component, depending on the event's name. Also changes the return type allowed from an event listener to allow passing an asynchronous function. This is a pretty common thing to do (the need to use await), but TypeScript/linters will often yell at you for doing so if the event listener does not expect a Promise to be returned.

(also fixed a typing issue with NodeJS.Timer being used instead of number)

@carsonbot carsonbot added the Status: Needs Review Needs to be reviewed label Apr 5, 2024
@smnandre
Copy link
Member

smnandre commented Apr 5, 2024

Thank you for that @YummYume !

Could you rebuild the dist file ? It feels you updated those files "manually" (i may be wrong on this, TS is not my strongest force 😅 )

From the root of the monorepo, this should be enough i think

npm run build

(This is why the CI is red Symfony UX / Check for UnBuilt JS Dist Files (pull_request) Failing after 1m)

@YummYume
Copy link
Contributor Author

YummYume commented Apr 5, 2024

This is weird 🤔 I didn't touch the dist files I simply ran the yarn build command. And somehow the Autocomplete controller needed to be rebuilt. Now it's fine.

No idea why the LiveComponent tests aren't passing, though, they're all green.

@smnandre
Copy link
Member

smnandre commented Apr 5, 2024

Related to dependencies / Symfony 7.1.. and don't bother with Autocomplete if you're not working on it ... or you won't ever end this PR haha

@@ -55,7 +55,7 @@ export default class {
};
}

const timer = setInterval(() => {
const timer = window.setInterval(() => {
Copy link
Contributor

Choose a reason for hiding this comment

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

I am not sure about this fix. setInterval is native function well supported why use the one from window?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Using setInterval or window.setInterval results in the same thing in a browser context. However, when using setInterval, TypeScript was mistakenly thinking that this was the Nodejs setInterval function, which doesn't return a number like in a browser, which was causing a typing error warning during builds.

Copy link
Contributor

Choose a reason for hiding this comment

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

Ok thanks for your answer!

@@ -30,8 +33,34 @@ export default class Component {
addPlugin(plugin: PluginInterface): void;
connect(): void;
disconnect(): void;
on(hookName: string, callback: (...args: any[]) => void): void;
off(hookName: string, callback: (...args: any[]) => void): void;
on(hookName: 'connect', callback: (component: Component) => MaybePromise): void;
Copy link
Contributor

Choose a reason for hiding this comment

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

Instead of having all of those definitions why not just use type ComponentHookName?
on(hookName: ComponentHookName, callback: (component: Component) => MaybePromise): void;

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Because the callbacks receive different arguments depending on the event. By explicitly overloading this method, TypeScript (and your IDE) now knows which events are available, and the arguments that they each receive. It's a much nicer developer experience, and it should work for any IDE even when not using TypeScript.

@@ -122,11 +126,31 @@ export default class Component {
* * loading.state:finished (element: HTMLElement) => {}
* * model:set (model: string, value: any, component: Component) => {}
*/
on(hookName: string, callback: (...args: any[]) => void): void {
on(hookName: 'connect', callback: (component: Component) => MaybePromise): void;
Copy link
Contributor

Choose a reason for hiding this comment

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

What do you think about adding callback types too? Like ConnectHook or ConnectCallback

Copy link
Contributor

Choose a reason for hiding this comment

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

Just a suggestion, I'm not so comfortable with TypeScript, but what would you think about something like that?

type Hooks = {
    'connect': (component: Component) => MaybePromise,
    'disconnect': (component: Component) => MaybePromise,
    'request:started': (requestConfig: any) => MaybePromise
    'render:finished': (component: Component) => MaybePromise
    'response:error': (backendResponse: BackendResponse, controls: {displayError: boolean}) => MaybePromise
    'loading.state.started': (element: HTMLElement, request: BackendRequest) => MaybePromise
    'loading.state.finished': (element: HTMLElement) => MaybePromise
    'model:set': (model: string, value: any, component: Component) => MaybePromise,
}

type ComponentHookName = keyof Hooks;

type HookCallback<T extends string> = T extends ComponentHookName
    ? Hooks[T]
    : (...args: any[]) => MaybePromise;

class Component {
    on<T extends ComponentHookName|string>(name: T, callback: HookCallback<T>): void {
        this.hooks.register(hookName, callback);
    }
    off<T extends ComponentHookName|string>(name: T, callback: HookCallback<T>): void {
        this.hooks.unregister(hookName, callback);
    }
}

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hi @squrious ! I tested this approach, and after a few tweaks, it gives the same result as the current implementation, and is a bit easier to use for maintaining many events, rather than overloading the methods manually. I personally wouldn't be opposed to it. 😄

Copy link
Contributor

Choose a reason for hiding this comment

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

Nice 😄

Copy link
Contributor Author

Choose a reason for hiding this comment

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

So I went ahead and implemented your idea with the conditional, it's way more maintainable and achieves the same thing. 👍

@YummYume YummYume force-pushed the ts/component-hook-names branch from 1062315 to de5dbd2 Compare April 20, 2024 10:14
@carsonbot carsonbot added Status: Reviewed Has been reviewed by a maintainer and removed Status: Needs Review Needs to be reviewed labels Apr 27, 2024
Copy link
Contributor

@WebMamba WebMamba left a comment

Choose a reason for hiding this comment

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

Sounds good to me! Thanks a lot @YummYume!

@@ -55,7 +55,7 @@ export default class {
};
}

const timer = setInterval(() => {
const timer = window.setInterval(() => {
Copy link
Contributor

Choose a reason for hiding this comment

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

Ok thanks for your answer!

@kbond kbond force-pushed the ts/component-hook-names branch from de5dbd2 to 3c17279 Compare April 30, 2024 15:30
@kbond
Copy link
Member

kbond commented Apr 30, 2024

Awesome first contribution @YummYume, thank you!

@kbond kbond merged commit db88684 into symfony:2.x Apr 30, 2024
37 checks passed
@YummYume YummYume deleted the ts/component-hook-names branch June 9, 2024 10:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Status: Reviewed Has been reviewed by a maintainer
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants