How to get started with WordPress Hooks in Javascript
Tags: wordpress, javascript
Published: 2021-08-17 15:54:33

WordPress has provided hooks in PHP for years and they recently released a version for Javascript. Hooks allow different parts of your code to augment values or perform additional actions. When referring to actions and filters, we'll be using the term "hooks" to discuss both. If you see the words "action" or "filter", assume we're discussing functionality that only relates to only that hook.

## How to start using WordPress hooks in Javascript

Before handing WordPress hooks in Javascript, make sure you are coding in a JS file that has access to the global `wp` javascript variable, or that you are using the `@wordpress/hooks` npm package

This article will be using the wp global variables hook endpoints for coding examples. If you are enqueuing your javascript correctly in WordPress, you should already have access to this variable. You can confirm this by doing `console.log(wp.hooks)`

## Adding your own hooks

Adding actions and filters in Javascript is similar to PHP:

```jsx
// actions
wp.hooks.addAction('hookName', 'namespace', callback, priority)

// filters
wp.hooks.addFilter('hookName', 'namespace', callback, priority)
```

The parameters for both hook types are:
* **hookName**: the name of your action or filter, which you'll use to call the hook
* **namespace**: a custom namespace (for example: your product name).
* **callback**: the function that will execute your hook 
* **priority**: the order in which your hook fires

I'm not sure why the namespace parameter exists because you don't use it when calling a hook (although you do when removing one). To prevent duplicate hook names, I would recommend adding a namespace to your hook name as well.

## Applying hooks in your code
Just like with PHP, you have access to a couple of functions to call your filters and actions:

```jsx
// actions
wp.hooks.doAction('hookName', ..args)

// filters
wp.hooks.applyFilters('hookName', 'content', ...args)
```

Notice how we don't refer to the namespace here, which is why I recommend adding one to your hook name.

_Reminder: filters take a default value before the args list, and should return a value to a variable. Actions should execute code and not return anything._

With these four API methods, you can implement a Javascript version of WordPress's hooks system.

## Troubleshooting when hooks don't fire in the correct order

An issue I ran into the first time I tried to use the `wp.hooks.addFilter` method was inconsistent filter execution.

I was trying to pass a string through two `addFilter` calls in separate plugins and I wanted the end value to include any modifications in each callback. Here is how I enqueued my javascript files in PHP (split between two plugin.php files):

```php
// first plugin
wp_enqueue_script(
    'ndx-first-plugin-script', 
    plugin_dir_url(__FILE__) . 'dist/js/first.js', 
    [], 
    '1.0.0', 
    true
);

// second plugin
wp_enqueue_script(
    'ndx-second-plugin-script',
     plugin_dir_url(__FILE__) . 'dist/js/second.js',
     [],
     '1.0.0',
     true
);
```

In my first.js file I created a filter that would return "I am the beginning" prepended to a passed in value:

```jsx
// ndx-first-plugin-script (first.js)
wp.hooks.addFilter(
	"ndx_change_string",
	"index",
	(value) => {
		return `I am the beginning. ${value}`
	},
	10
)
```

In my second.js file, I added the same filter hook with a higher (read: lesser) priority and appended "I am the end" to the passed-in value:

```jsx
// ndx-second-plugin-script (second.js)
wp.hooks.addFilter(
	"ndx_change_string",
	"index",
	(value) => {
		return `${value} I am the end.`
	},
	20
)

console.log(wp.hooks.applyFilters("ndx_change_string", ""))
```

Because my apply calls passed in empty strings I expected the result: "I am the beginning. I am the end." but what I got was "I am the end."

Unfortunately, my second plugin's javascript loads first and doesn't see the first plugin's filter value (this can happen in PHP too, by the way). You will generally use filters to augment a standalone value, but it's also common to use them to add and remove items from arrays so sometimes the order is important.

There are a couple of ways to solve this problem:

Ensure the plugin where you plan to call `applyFilters` is enqueued as a dependency in your other scripts:

```php
// second plugin
wp_enqueue_script(
    'ndx-second-plugin-script',
     plugin_dir_url(__FILE__) . 'dist/js/second.js',
    ['ndx-first-plugin-script'], // add your first plugin script as a required dependency
    '1.0.0',
    true
);
```

Or utilize the built-in `hookAdded` method to call additional filters after the first one is created:

```jsx
wp.hooks.addAction(
	"hookAdded",
	"core/i18n",
	(name, functionName, callback, priority) => {
		if(name == "ndx_change_string" && priority == 10) {
	           // add additional filters here after the first one fires
		}
	},
	10
)
```

Note that the `hookAdded` method can be fragile and buggy and it would be very easy to get stuck in an infinite loop if two addFilter calls had a priority of 10.