Cat logomenu

How to Patch an npm Package with yarn patch

One of the new features of yarn 2.0 is the ability to modify an installed package in-place, so you can incorporate your own changes or bugfixes without having to wait for the package maintainer to make those changes—or, indeed, so you can make changes you think the maintainer will not make at all.1. I needed to do this recently for a bugfix in a popular package2 that I was using in a client’s React application, and I found that specific step-by-step instructions on creating a patch were a bit thin on the ground, so here we go.

Before we get to the actual instructions, a summary of my particular problem: I was using material-table, which had a recently added feature that lets you set individual cells in the table as editable by the user. Unfortunately, clicking into one of those editable cells to make the input active threw this dev error into the browser console:

Warning: Unsupported style property aria-label. Did you mean ariaLabel?

I’ll leave out the massive stacktrace below the error and just skip to the cause of the issue: Whoever wrote this feature had, in several places, passed the underlying material-ui <TextField> component something like this:

InputProps={{
  style: {
    fontSize: 13
  },
  inputProps: {
    "aria-label": `${this.props.columnDef.title}: press space to edit`
  }
}}

Not to get in the weeds about how the material-ui library works, this should have been:

InputProps={{
  style: {
    fontSize: 13
  }
}}
inputProps={{
    "aria-label": `${this.props.columnDef.title}: press space to edit`
}}

Probably just a bad copy-and-paste, one imagines. The mistake resulted in the error above, as well as, of course, the <input> element rendered in the DOM not receiving a proper aria-label, which is bad for accessibility.

Once I had verified that this fix worked, I submitted a PR for the change. But I still wanted to be able to use material-table without waiting for my PR to be reviewed and merged. They’re pretty on top of things in that repo, but I still wasn’t sure how long it would be.

Enter yarn patch. Now, the unfortunate thing about using yarn patch is that you have to modify the actual distributed code, not the source. This is often much less friendly, because it’s been babelized to within an inch of its life. So let the patcher beware, I guess. In the case of my particular bug, it wasn’t too bad, but you never know. Anyway, here’s how it works:

First, you need to have the package already installed:

yarn add material-table

Then you can run yarn patch material-table

At this point, you may run into the error I did in my client’s application:

Usage Error: Multiple candidate packages found; explicitly choose one of them (use `yarn why <package>` to get more information as to who depends on them):

This will be followed by a list of the “multiple candidates”. Here was mine:

- material-table@npm:1.68.0
- material-table@npm:1.68.0 [bd58c]

If you want to see how these two packages are being used, just follow the instructions in the Usage Error:

yarn why material-table

Here’s what I got:

<client-app-name-directory>:> yarn why material-table
└─ <client-app-name>@workspace:.
   └─ material-table@npm:1.68.0 [bd58c]

Now, I gotta tell ya… I spent a lot of time trying to figure out how to run yarn patch material-table@npm:1.68.0 [bd58c] successfully. The space between the version number and what I assume is some specific hash of the package completely flummoxed me; nothing seemed to escape it correctly. The yarn patch Usage Error was already next to impossible to google (possibly because of the prevalence of non-programming uses of the word “yarn”), and this was even worse. I eventually gave up, hoping (correctly) that it wouldn’t matter. A search through my yarn.lock file told me that no other packages in my app were using material-table, so I suspected that there wouldn’t be any meaningful difference between the two. Anyway, I just went with the version I could successfully pass to the command:

yarn patch material-table@npm:1.68.0

This command executes fairly quickly and gives you something like this:

➤ YN0000: Package material-table@npm:1.68.0 got extracted with success!
➤ YN0000: You can now edit the following folder: /private/var/folders/gv/0qnsf23537x1fj9hmcvd3r900000gn/T/xfs-cb48a728
➤ YN0000: Once you are done run yarn patch-commit /private/var/folders/gv/0qnsf23537x1fj9hmcvd3r900000gn/T/xfs-cb48a728 and Yarn will store a patchfile based on your changes.
➤ YN0000: Done in 0.06s

(I won’t make any guarantees about the specific path to the working files; make sure you check your own output.)

Navigating to the specified directory (on a Mac, switching to the Finder and typing Cmd-Shift-G to open a window where you can paste the path is very much your friend at times like this) will show you a copy of the original npm module. Here’s where you’ll make your changes. For me, that was changing a bunch of things like this:

InputProps: {
  style: {
    fontSize: 13
  },
  inputProps: {
    "aria-label": "".concat(this.props.columnDef.title, ": press space to edit")
  }
}

to this:

InputProps: {
  style: {
    fontSize: 13
  }
},
inputProps: {
  "aria-label": "".concat(this.props.columnDef.title, ": press space to edit")
}

You’ll note that this looks similar but not identical to the source for the reason I mentioned above.

Once you’ve finished making your changes, you’ll save the file in place. If you use an IDE that does any kind of auto-format on save, you’ll want to bypass this. As you’ll see in a moment, the patch-commit command is going to create a very specific diff file, and you don’t want it cluttered with cosmetic changes.

Now that you’ve saved your changes, switch back to the terminal and run the command provided to you by the output of yarn patch. By default, yarn will output the contents of the patch directly to the terminal. To make life easier, toss this into a file somewhere convenient to you. For example:

yarn patch-commit /PATH/TO/YOUR/WORKING/DIRECTORY > ~/Desktop/material-table.patch

The contents of the patch file are going to look something like this:

diff --git a/dist/components/m-table-edit-field.js b/dist/components/m-table-edit-field.js
index 36f3c4fd..adb990a7 100644
--- a/dist/components/m-table-edit-field.js
+++ b/dist/components/m-table-edit-field.js
@@ -170,9 +170,9 @@ var MTableEditField = /*#__PURE__*/function (_React$Component) {
           style: {
             fontSize: 13
           },
-          inputProps: {
-            "aria-label": "".concat(this.props.columnDef.title, ": press space to edit")
-          }
+        },
+        inputProps: {
+          "aria-label": "".concat(this.props.columnDef.title, ": press space to edit")
         }
       })));
     }
@@ -190,10 +190,10 @@ var MTableEditField = /*#__PURE__*/function (_React$Component) {
         InputProps: {
           style: {
             fontSize: 13
-          },
-          inputProps: {
-            "aria-label": "".concat(this.props.columnDef.title, ": press space to edit")
           }
+        },
+        inputProps: {
+          "aria-label": "".concat(this.props.columnDef.title, ": press space to edit")
         }
       })));
     }
@@ -216,10 +216,10 @@ var MTableEditField = /*#__PURE__*/function (_React$Component) {
         InputProps: {
           style: {
             fontSize: 13
-          },
-          inputProps: {
-            "aria-label": this.props.columnDef.title
           }
+        },
+        inputProps: {
+          "aria-label": this.props.columnDef.title
         }
       }));
     }
@@ -244,13 +244,15 @@ var MTableEditField = /*#__PURE__*/function (_React$Component) {

           return _this4.props.onChange(value);
         },
-        inputProps: {
+        InputProps: {
           style: {
             fontSize: 13,
             textAlign: "right",
-            "aria-label": this.props.columnDef.title
           }
         },
+        inputProps: {
+          "aria-label": this.props.columnDef.title
+        },
         onKeyDown: this.props.onKeyDown,
         autoFocus: this.props.autoFocus
       }));

3

Now move this file into your application somewhere. In my client’s app, we keep these in a ./patches/ directory, but you organize as you prefer.

We’re almost done! Now we just need to tell the application we want to use the patch. Skip on over to your package.json and update the entry for the package you’re patching with this syntax:

- "my-installed-package": "1.0.0"
+ "my-installed-package": "patch:my-installed-package@1.0.0#PATH/TO/PATCH/FILE"

In my case, that meant changing

"material-table": "1.68.0"

to

"material-table": "patch:material-table@1.68.0#./patches/material-table.patch"

Now re-run yarn and restart your dev environment. Et voilà… Your patch is live! Don’t forget to be a good citizen and submit a PR for your change to the original repo.


  1. My current take on this is that you should probably avoid using yarn patch to implement your own new features; it seems more suitable for bugfixes or small tweaks. If you want new functionality from a third-party package, just fork it.
  2. Also my opinion: Patching a bugfix like this is no substitute for submitting a GitHub Issue or PR to get the bug fixed in the actual package.
  3. In case you’re wondering: No, I did not forget to properly format the spacing in my code block; the patch actually retains all the spaces from the original, so I left them in.

Give us a share!