From 1f6abc2453c4176d76a5866fd71ce595cd770e80 Mon Sep 17 00:00:00 2001 From: Parman Mohammadalizadeh Date: Fri, 18 Sep 2026 19:38:23 +0200 Subject: [PATCH 1/2] Move the arrow function anti-example inside its explanation's details tag The details tag held the explanation of why arrow functions are wrong here, but the anti-example itself sat outside it. With the details collapsed a reader saw two code blocks and nothing marking the first as the one not to copy. --- contributor_docs/creating_libraries.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/contributor_docs/creating_libraries.md b/contributor_docs/creating_libraries.md index bd682cf198..77e9178c2c 100644 --- a/contributor_docs/creating_libraries.md +++ b/contributor_docs/creating_libraries.md @@ -126,7 +126,6 @@ You can access p5.js functions and variables such as `circle()` and `PI` in your
You should always use the “function()” keyword to attach methods to the fn argument object. Don’t use the arrow function syntax “() =>” because the value of “this” when using the “function()” keyword is the created object (i.e., the p5 sketch), but with the arrow function syntax, the value of “this” is whatever the value of “this” is when the arrow function is defined. In the example below, “this” will refer to “window” instead of the p5 sketch, which is usually not what we want. -
```js function loadCSVAddon(p5, fn, lifecycles) { @@ -139,6 +138,8 @@ function loadCSVAddon(p5, fn, lifecycles) { } ``` + + ```js function loadCSVAddon(p5, fn, lifecycles) { fn.loadCSV = function (filename) { From 7c83789fac4ddc0438e14f9eea7b6b9f2d04e691 Mon Sep 17 00:00:00 2001 From: Parman Mohammadalizadeh Date: Fri, 18 Sep 2026 19:41:27 +0200 Subject: [PATCH 2/2] Document how an addon stores state Covers both kinds: per-sketch state defaulted on fn and written through this, and renderer state written with states.setValue() so that push() and pop() can restore it. Notes why a variable in the addon function's scope is shared across sketches, and why assigning to states directly leaks past the pop() that should undo it. --- contributor_docs/creating_libraries.md | 48 ++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/contributor_docs/creating_libraries.md b/contributor_docs/creating_libraries.md index 77e9178c2c..cbaa745247 100644 --- a/contributor_docs/creating_libraries.md +++ b/contributor_docs/creating_libraries.md @@ -315,6 +315,54 @@ Please note that in the above example, if the user does not define `function myA Overall, this custom actions approach supports accessing the custom action functions in both global mode and instance mode with the same code, simplifying your code from what it otherwise may need to be. +## Adding state to your addon + +Most addons need to remember something between calls. p5.js keeps two kinds of state in two different places, and the difference that matters is whether `push()` and `pop()` should restore it. + +**State that** `push()` **and** `pop()` **leave alone** + +This is ordinary per-sketch state, such as a cache or a configuration flag. Give it a default on the `fn` argument, which is an alias for `p5.prototype`, then read and write it through `this` inside your methods and lifecycle hooks. + +```js +function loadCSVAddon(p5, fn, lifecycles) { + fn.csvCache = null; + + fn.loadCSV = async function (filename) { + this.csvCache = await fetch(filename).then((response) => response.text()); + return this.csvCache; + }; +} +``` + +The default on `fn` is shared, but the first write through `this` creates a property on that sketch instance which shadows it. Each sketch on the page therefore gets its own value from the moment it writes one. + +It is worth being deliberate about this, because the shorter-looking alternative does not do the same thing. A variable declared in the addon function's own scope reads like addon state and is not: + +```js +function loadCSVAddon(p5, fn, lifecycles) { + let csvCache = null; // shared by every sketch on the page + + fn.loadCSV = async function (filename) { + csvCache = await fetch(filename).then((response) => response.text()); + return csvCache; + }; +} +``` + +The addon function runs once when the addon is registered, not once per sketch, so every p5 instance on the page reads and writes that same variable. With two sketches loaded, one will overwrite the other's cache. + +**State that** `push()` **and** `pop()` **restore** + +Drawing state belongs on the renderer, at `this._renderer.states`, alongside built-in state such as the current fill and stroke weight. Write it with `setValue()` rather than by assignment: + +```js +this._renderer.states.setValue('myProperty', value); +``` + +Assigning directly, as in `this._renderer.states.myProperty = value`, appears to work and then quietly breaks `pop()`. + +The reason is how the restore is implemented. A `States` object keeps a private record of which keys have changed since the last `push()`, and `setValue()` is what writes to that record, saving the previous value the first time a key changes. `push()` takes that record and puts it on a stack, and `pop()` takes it back off and reinstates the saved values. A direct assignment changes the value without ever telling the record, so when `pop()` runs there is nothing to restore and the change survives past the `pop()` that should have undone it. + ## Next steps Below are some extra tips about authoring your addon library.