Skip to content

Commit

Permalink
Provide context object as an argument in lifecycle hooks (#2090)
Browse files Browse the repository at this point in the history
To help improve the API of lifecycle hooks, this PR updates arguments so that there is always a "context" object provided as the argument. This removes the need to call methods using `call(this)` or `bind(this)` since whatever you expect `this` to be, it can be more explicitly accessed via the context object. This takes the following form depending on which hook is being run:

```js
{ parent, entry, plugin }
```
- Parent: this is the root component class (Modal, Drawer, Popover, etc). It is always provided to all lifecycle hook calls.
- Entry: this is the current entry object being acted upon. This is provided only when an entry context is available, e.g: `onMount`, `beforeRegister`, `afterRegister`.
- Plugin: this is the plugin object that is running the hook. For example, any hook or `onChange` callbacks will have this context available.

**Additional changes**

- `maybeRunMethod` has been refactored to no longer set `this`, but instead takes an `obj` argument.
- `debug` has been expanded to provide more control and information of log output.
- `condition` options in both `propStore` and `debug` can now either be a boolean value or a function that returns a boolean.
  • Loading branch information
sebnitu authored Oct 23, 2024
1 parent a40fda5 commit 86e6392
Show file tree
Hide file tree
Showing 17 changed files with 138 additions and 109 deletions.
6 changes: 3 additions & 3 deletions docs/src/modules/useDrawer.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ if (typeof window !== "undefined") {
plugins: [
propStore({
prop: "inlineState",
value: (entry) => entry.store,
condition: (entry) => ["opened", "closed"].includes(entry.state),
onChange: (entry) => entry.applyState()
value: ({ entry }) => entry.store,
condition: ({ entry }) => ["opened", "closed"].includes(entry.state),
onChange: ({ entry }) => entry.applyState()
}),
mediaQuery({
onChange(event, entry) {
Expand Down
44 changes: 22 additions & 22 deletions packages/core/src/js/Collection.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,20 +41,20 @@ export class Collection {
await entry.mount();

// beforeRegister lifecycle hooks.
await maybeRunMethod.call(this, "beforeRegister", entry);
await maybeRunMethod.call(entry, "beforeRegister");
await maybeRunMethod(this, "beforeRegister", { parent: this, entry});
await maybeRunMethod(entry, "beforeRegister", { parent: this, entry});
for (const plugin of this.plugins) {
await maybeRunMethod.call(plugin, "beforeRegister", entry);
await maybeRunMethod(plugin, "beforeRegister", { plugin, parent: this, entry});
}

// Add the entry to the collection.
this.collection.push(entry);

// afterRegister lifecycle hooks.
await maybeRunMethod.call(this, "afterRegister", entry);
await maybeRunMethod.call(entry, "afterRegister");
await maybeRunMethod(this, "afterRegister", { parent: this, entry});
await maybeRunMethod(entry, "afterRegister", { parent: this, entry});
for (const plugin of this.plugins) {
await maybeRunMethod.call(plugin, "afterRegister", entry);
await maybeRunMethod(plugin, "afterRegister", { plugin, parent: this, entry});
}

return entry;
Expand All @@ -68,10 +68,10 @@ export class Collection {
await entry.unmount(reReg);

// beforeDeregister lifecycle hooks.
await maybeRunMethod.call(this, "beforeDeregister", entry, reReg);
await maybeRunMethod.call(entry, "beforeDeregister", reReg);
await maybeRunMethod(this, "beforeDeregister", { parent: this, entry}, reReg);
await maybeRunMethod(entry, "beforeDeregister", { parent: this, entry}, reReg);
for (const plugin of this.plugins) {
await maybeRunMethod.call(plugin, "beforeDeregister", entry, reReg);
await maybeRunMethod(plugin, "beforeDeregister", { plugin, parent: this, entry}, reReg);
}

// Remove all the owned properties from the entry.
Expand All @@ -85,10 +85,10 @@ export class Collection {
this.collection.splice(index, 1);

// afterDeregister lifecycle hooks.
await maybeRunMethod.call(this, "afterDeregister", entry, reReg);
await maybeRunMethod.call(entry, "afterDeregister", reReg);
await maybeRunMethod(this, "afterDeregister", { parent: this, entry}, reReg);
await maybeRunMethod(entry, "afterDeregister", { parent: this, entry}, reReg);
for (const plugin of this.plugins) {
await maybeRunMethod.call(plugin, "afterDeregister", entry, reReg);
await maybeRunMethod(plugin, "afterDeregister", { plugin, parent: this, entry}, reReg);
}
}

Expand All @@ -101,14 +101,14 @@ export class Collection {

// Mount plugins.
for (const plugin of this.plugins) {
await maybeRunMethod.call(plugin, "mount", this);
await maybeRunMethod(plugin, "mount", { plugin, parent: this});
}

// beforeMount lifecycle hooks.

await maybeRunMethod.call(this, "beforeMount");
await maybeRunMethod(this, "beforeMount");
for (const plugin of this.plugins) {
await maybeRunMethod.call(plugin, "beforeMount", this);
await maybeRunMethod(plugin, "beforeMount", { plugin, parent: this});
}

// Get all the selector elements and register them.
Expand All @@ -118,19 +118,19 @@ export class Collection {
}

// afterMount lifecycle hooks.
await maybeRunMethod.call(this, "afterMount");
await maybeRunMethod(this, "afterMount");
for (const plugin of this.plugins) {
await maybeRunMethod.call(plugin, "afterMount", this);
await maybeRunMethod(plugin, "afterMount", { plugin, parent: this});
}

return this;
}

async unmount() {
// beforeUnmount lifecycle hooks.
await maybeRunMethod.call(this, "beforeUnmount");
await maybeRunMethod(this, "beforeUnmount");
for (const plugin of this.plugins) {
await maybeRunMethod.call(plugin, "beforeUnmount", this);
await maybeRunMethod(plugin, "beforeUnmount", { plugin, parent: this});
}

// Loop through the collection and deregister each entry.
Expand All @@ -139,14 +139,14 @@ export class Collection {
}

// afterUnmount lifecycle hooks.
await maybeRunMethod.call(this, "afterUnmount");
await maybeRunMethod(this, "afterUnmount");
for (const plugin of this.plugins) {
await maybeRunMethod.call(plugin, "afterUnmount", this);
await maybeRunMethod(plugin, "afterUnmount", { plugin, parent: this});
}

// Unmount plugins.
for (const plugin of this.plugins) {
await maybeRunMethod.call(plugin, "unmount", this);
await maybeRunMethod(plugin, "unmount", { plugin, parent: this});
}

return this;
Expand Down
8 changes: 4 additions & 4 deletions packages/core/src/js/CollectionEntry.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,17 +41,17 @@ export class CollectionEntry {
this.getCustomProps();

// On mount lifecycle hooks.
await maybeRunMethod.call(this, "onMount");
await maybeRunMethod(this, "onMount", { parent: this.parent, entry: this });
for (const plugin of this.parent.plugins) {
await maybeRunMethod.call(plugin, "onMount", this);
await maybeRunMethod(plugin, "onMount", { plugin, parent: this.parent, entry: this });
}
}

async unmount(reMount = false) {
// Before mount lifecycle hooks.
await maybeRunMethod.call(this, "onUnmount", reMount);
await maybeRunMethod(this, "onUnmount", { parent: this.parent, entry: this }, reMount);
for (const plugin of this.parent.plugins) {
await maybeRunMethod.call(plugin, "onUnmount", this, reMount);
await maybeRunMethod(plugin, "onUnmount", { plugin, parent: this.parent, entry: this }, reMount);
}
}
}
2 changes: 1 addition & 1 deletion packages/core/src/js/helpers/pluginsArray.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ export class pluginsArray extends Array {
async remove(name) {
const index = this.findIndex((plugin) => plugin.name === name);
if (~index) {
await maybeRunMethod.call(this[index], "unmount", this.parent);
await maybeRunMethod(this[index], "unmount", { parent: this.parent });
this.splice(index, 1);
}
}
Expand Down
66 changes: 45 additions & 21 deletions packages/core/src/js/plugins/debug.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ import { createPluginObject } from "../helpers";

const defaults = {
color1: "color: hsl(152deg 60% 40%)",
color2: "color: hsl(152deg 60% 50%)"
color2: "color: hsl(152deg 60% 50%)",
condition: true
};

export function debug(options = {}) {
Expand All @@ -11,53 +12,76 @@ export function debug(options = {}) {
settings: {...defaults, ...options}
};

function log(string) {
function log(string, args) {
console.log(
`%cDEBUG: %c${string}`,
`%c📡 DEBUG: %c${string}`,
props.settings.color1,
props.settings.color2,
...args
);
}

function getValue(obj, ...args) {
return (typeof obj === "function") ? obj(...args) : obj;
}

const methods = {
// Mount lifecycle hooks...
mount() {
log("mountPlugins()");
log("mountPlugins()", arguments);
},
beforeMount() {
log("beforeMount()");
log("beforeMount()", arguments);
},
onMount() {
log("onMount()");
onMount({ parent, entry }) {
if (getValue(this.settings.condition, { parent, entry })) {
const count = parent.collection.length;
log(`onMount() > [${count}] #${entry.id}`, arguments);
}
},
beforeRegister() {
log("beforeRegister()");
beforeRegister({ parent, entry }) {
if (getValue(this.settings.condition, { parent, entry })) {
const count = parent.collection.length;
log(`beforeRegister() > [${count}] #${entry.id}`, arguments);
}
},
afterRegister() {
log("afterRegister()");
afterRegister({ parent, entry }) {
if (getValue(this.settings.condition, { parent, entry })) {
const count = parent.collection.length - 1;
log(`afterRegister() > [${count}] #${entry.id}`, arguments);
}
},
afterMount() {
log("afterMount()");
log("afterMount()", arguments);
},

// Unmount lifecycle hooks...
beforeUnmount() {
log("beforeUnmount()");
log("beforeUnmount()", arguments);
},
onUnmount() {
log("onUnmount()");
onUnmount({ parent, entry }) {
if (getValue(this.settings.condition, { parent, entry })) {
const count = parent.collection.length - 1;
log(`onUnmount() > [${count}] #${entry.id}`, arguments);
}
},
beforeDeregister() {
log("beforeDeregister()");
beforeDeregister({ parent, entry }) {
if (getValue(this.settings.condition, { parent, entry })) {
const count = parent.collection.length - 1;
log(`beforeDeregister() > [${count}] #${entry.id}`, arguments);
}
},
afterDeregister() {
log("afterDeregister()");
afterDeregister({ parent, entry }) {
if (getValue(this.settings.condition, { parent, entry })) {
const count = parent.collection.length;
log(`afterDeregister() > [${count}] #${entry.id}`, arguments);
}
},
afterUnmount() {
log("afterUnmount()");
log("afterUnmount()", arguments);
},
unmount() {
log("unmountPlugins()");
log("unmountPlugins()", arguments);
}
};

Expand Down
11 changes: 5 additions & 6 deletions packages/core/src/js/plugins/mediaQuery.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { getPrefix } from "../helpers/getPrefix";
import { createPluginObject } from "../helpers";
import { createPluginObject, getPrefix } from "../helpers";

const defaults = {
// The data attributes to get the breakpoint values from.
Expand Down Expand Up @@ -32,17 +31,17 @@ export function mediaQuery(options = {}) {
};

const methods = {
unmount(context) {
context.collection.forEach((entry) => {
unmount({ parent }) {
parent.collection.forEach((entry) => {
removeMediaQueryList(entry);
});
},

onMount(entry) {
onMount({ entry }) {
setupMediaQueryList(entry);
},

onUnmount(entry) {
onUnmount({ entry }) {
removeMediaQueryList(entry);
}
};
Expand Down
30 changes: 17 additions & 13 deletions packages/core/src/js/plugins/propStore.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ const defaults = {
// will be used e.g: "VB:ModalState".
key: null,
// Condition to determine whether or not to store the value in local storage.
condition: () => false,
condition: false,
// The function to run whenever the value changes.
onChange() {}
};
Expand All @@ -25,28 +25,29 @@ export function propStore(options = {}) {
};

const methods = {
mount(parent) {
mount({ parent }) {
this.store = localStore(getKey(parent.module));
},

unmount(context) {
context.collection.forEach((entry) => {
this.onUnmount(entry);
unmount({ parent }) {
parent.collection.forEach((entry) => {
removePropStore.call(this, entry);
});
},

async onMount(entry) {
async onMount({ entry }) {
await setupPropStore.call(this, entry);
},

onUnmount(entry) {
onUnmount({ entry }) {
removePropStore.call(this, entry);
}
};

async function setupPropStore(entry) {
// Store the initial property value. Set to null if property doesn't exist.
let _value = entry[this.settings.prop] || null;
const contextObj = { plugin: this, parent: entry.parent, entry };

// Define a getter and setter for the property.
Object.defineProperty(entry, this.settings.prop, {
Expand All @@ -60,11 +61,12 @@ export function propStore(options = {}) {
const oldValue = _value;
_value = newValue;
// Conditionally store the value in local storage.
if (this.settings.condition(entry, newValue, oldValue)) {
this.store.set(entry.id, _value);
const condition = getValue(this.settings.condition, contextObj, newValue, oldValue);
if (condition) {
this.store.set(entry.id, newValue);
}
// Run the on change callback.
await this.settings.onChange.call(this, entry, newValue, oldValue);
await this.settings.onChange(contextObj, newValue, oldValue);
},
});

Expand All @@ -80,9 +82,11 @@ export function propStore(options = {}) {
});

// Conditionally set the initial value. Must be a truthy value.
entry[this.settings.prop] = (typeof this.settings.value === "function") ?
this.settings.value.call(this, entry) || entry[this.settings.prop] :
this.settings.value || entry[this.settings.prop];
entry[this.settings.prop] = await getValue(this.settings.value, contextObj) || entry[this.settings.prop];
}

function getValue(obj, ...args) {
return (typeof obj === "function") ? obj(...args) : obj;
}

async function removePropStore(entry) {
Expand Down
8 changes: 4 additions & 4 deletions packages/core/src/js/plugins/teleport.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ export function teleport(options = {}) {
};

const methods = {
unmount(context) {
context.collection.forEach((entry) => {
unmount({ parent }) {
parent.collection.forEach((entry) => {
if (typeof entry.teleportReturn === "function") {
entry.teleportReturn();
delete entry.teleport;
Expand All @@ -23,12 +23,12 @@ export function teleport(options = {}) {
});
},

onMount(entry) {
onMount({ entry }) {
entry.teleport = teleport.bind(this, entry);
entry.teleport();
},

onUnmount(entry) {
onUnmount({ entry }) {
teleportReturn(entry);
}
};
Expand Down
Loading

0 comments on commit 86e6392

Please sign in to comment.