question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Feature: Ensure the type passed to `createCommand` is retained, inferred, and enforced

See original GitHub issue

Issue created from a Q-and-A post over at Discord: https://discord.com/channels/953974421008293909/955972012541628456/990966752110325771

I really dig the command system in Lexical, but there are two things that irks me a little bit:

  1. Strongly typing the command seems like a great idea, but the underlying types makes it a bit hard to debug the code. Basically, createCommand()-call just creates an empty object {} that’s used as a unique key for a Map. This is great, but given that approach, whenever a command is dispatched, the type is simply {}. Would it make sense to have some internal field or name to trace which command is being dispatched?

  2. Each LexicalCommand is generic in terms of the payload, so you can have a LexicalCommand<boolean>, or LexicalCommand<KeyboardEvent>. However, the LexicalCommand<T> is really just an alias for Readonly<Record<string, unknown>>, so the required type of the payload isn’t saved. This also makes us lose type safety down the line, where you can do

const SOME_EVENT = createCommand<boolean>();

// ...

editor.dispatchCommand(SOME_EVENT, 'Not a boolean...');

and TypeScript will happily accept it. It’d be good if the payload was constrained by the type of the command here.

Additionally, when you registerCommand you need to repeat the type of the payload. Given that the type information was stored inside of the command itself, this could then be inferred, along the lines of:

editor.registerCommand(SOME_EVENT, (payload) => { /* do stuff with inferred boolean */ }

Finally, if the registerCommand could infer the payload type from the command itself, then we could ensure that the command handler has the expected signature. With the current approach there is nothing preventing a very similar error as in the dispatchCommand example, where the registerCommand-handler expects a payload differing from the one provided to createCommand.


As a starting point to the second bullet, I’ve created a PoC draft PR. There are still some type checking errors introduced by this change, related to LexicalEvents:

packages/lexical/src/LexicalEvents.ts:514:68 - error TS2345: Argument of type 'InputEvent' is not assignable
  to parameter of type 'string'.

514         dispatchCommand(editor, CONTROLLED_TEXT_INSERTION_COMMAND, event);
                                                                       ~~~~~

packages/lexical/src/LexicalEvents.ts:521:68 - error TS2345: Argument of type 'InputEvent' is not assignable
  to parameter of type 'string'.

521         dispatchCommand(editor, CONTROLLED_TEXT_INSERTION_COMMAND, event);
                                                                       ~~~~~

packages/lexical/src/LexicalEvents.ts:550:48 - error TS2345: Argument of type 'InputEvent' is not assignable
  to parameter of type 'ClipboardEvent'.
  Property 'clipboardData' is missing in type 'InputEvent' but required in type 'ClipboardEvent'.

550         dispatchCommand(editor, PASTE_COMMAND, event);
                                                   ~~~~~

  node_modules/typescript/lib/lib.dom.d.ts:3533:14
    3533     readonly clipboardData: DataTransfer | null;
                      ~~~~~~~~~~~~~
    'clipboardData' is declared here.

packages/lexical/src/LexicalEvents.ts:872:47 - error TS2345: Argument of type 'KeyboardEvent' is not 
  assignable to parameter of type 'ClipboardEvent'.
  Property 'clipboardData' is missing in type 'KeyboardEvent' but required in type 'ClipboardEvent'.

872         dispatchCommand(editor, COPY_COMMAND, event);
                                                  ~~~~~

  node_modules/typescript/lib/lib.dom.d.ts:3533:14
    3533     readonly clipboardData: DataTransfer | null;
                      ~~~~~~~~~~~~~
    'clipboardData' is declared here.

packages/lexical/src/LexicalEvents.ts:875:46 - error TS2345: Argument of type 'KeyboardEvent' is not
  assignable to parameter of type 'ClipboardEvent'.

875         dispatchCommand(editor, CUT_COMMAND, event);
                                                 ~~~~~

Issue Analytics

  • State:closed
  • Created a year ago
  • Reactions:3
  • Comments:5 (5 by maintainers)

github_iconTop GitHub Comments

2reactions
thegreatercurvecommented, Jun 27, 2022

Thanks for these @PAkerstrand. Your working solution to 2 seems really strong. We used to have payload to command type inference when we used Flow, but we must have lost that when we auto-generated the new TypeScript types.

If you have type issues by changing any of these to union type in any of the registerCommand callbacks, use instanceof in the registerCommand if we shouldn’t be calling any subsequent functions i.e.:

 editor.registerCommand(
      CUT_COMMAND,
      (event) => {
         const selection = $getSelection();
        if (!$isRangeSelection(selection)) {
          return false;
        }
        if (event instanceof ClipboardEvent) {
          onCutForPlainText(event, editor);        
        }
        return true;
      },
      COMMAND_PRIORITY_EDITOR,
    ),

The other issue you identified in 1. is definitely on my radar as it will affect our ability to add command logging as part of #2393. Sadly, I’m actually not sure if there’s a way around it without changing the existing API. We don’t want to require the developer to have to pass a type argument, but we also can’t access the instantiating variable name in the enclosing scope of createCommand.

Still not sure of a solution, but I will keep thinking.

1reaction
thegreatercurvecommented, Jun 28, 2022

@thegreatercurve I’m having some issues with various events not being available in the unit tests. I guess this is down to the testing environment in Jest and what’s exposed by the jsdom-environment?

 LexicalSelection tests › Paste text, move selection and delete word forward (#129)

    ReferenceError: ClipboardEvent is not defined

      381 |     const selection = $getSelection();
      382 |     const clipboardData =
    > 383 |       event instanceof ClipboardEvent ? event.clipboardData : null;
          |                        ^
      384 |     if (
      385 |       clipboardData != null &&
      386 |       ($isRangeSelection(selection) || $isGridSelection(selection))

      at packages/lexical-rich-text/src/index.ts:383:24
      at processNestedUpdates (packages/lexical/src/LexicalUpdates.ts:738:7)
      at beginUpdate (packages/lexical/src/LexicalUpdates.ts:796:22)
      at updateEditor (packages/lexical/src/LexicalUpdates.ts:902:5)
      at LexicalEditor.update (packages/lexical/src/LexicalEditor.ts:746:17)
      at packages/lexical-selection/src/__tests__/unit/LexicalSelection.test.tsx:159:20
      at Object.act (node_modules/react/cjs/react.development.js:2510:16)
      at update (packages/lexical-selection/src/__tests__/unit/LexicalSelection.test.tsx:158:26)
      at applySelectionInputs (packages/lexical-selection/src/__tests__/utils/index.ts:646:13)
      at Object.<anonymous> (packages/lexical-selection/src/__tests__/unit/LexicalSelection.test.tsx:949:33)

Any ideas whether it is fixable and how to set it up?

@PAkerstrand Interesting.

I think this might be a bug within Jest or JSDOM. From a quick experiment, it works the other way around if you do the check as event instanceof KeyboardEvent ? null : event.clipboardData;.

That should be enough to overcome the CI issues.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Your First Application: Take Two | InformIT - InformIT
Bart De Smet shows you how to to install the .NET Framework and the necessary tools, namely Visual Studio 2010. Bart then explains...
Read more >
Azure DevOps Server 2020 Release Notes - Microsoft Learn
Pipeline runs that have been manually retained or are retained by a release will not be deleted after the upgrade.
Read more >
Core Technologies - Spring
As of 3.1, it is defined as an xsd:string type. Note that bean id uniqueness is still enforced by the container, though no...
Read more >
SAP HANA SQLScript Reference for SAP HANA Platform
Ensure that the types of parameters and variables are as specific as ... The actual value passed for tabular OUT parameters must be`?`....
Read more >
4 Creating a Business Domain Layer Using Entity Objects
Importantly, it allows you to encapsulate domain business logic to ensure that ... declarative business logic features to enforce the validity of your...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found