T
TyphonJSTyphonJS (Michael)

Reactivty and prepareDerivedData

From @Wasp
So I'm using prepareDerivedData to, well, derive some data for my items; some of them are tied to each other (ie, an "equipment" type item affects a "skill" type item's bonus). I was hoping that re-calling the derived data methods would cause the TJSDocuments to pick up the alterations, but since it's not a database operation, it doesn't appear to pick that up. Is there a good way allow one item to cause the reactivity of another without manually creating stores?
Moving discusion to forum post.
M
Magus188d ago
You could create event listeners on the prepareDerivedData, the components could pick up the signals when prepareDerivedData is called and update their local values
TM
TyphonJS (Michael)188d ago
I definitely think Svelte 5 is going to provide a lot of flexibility in the near future for cases like this. IE all of your derived data can define $state and accessors. Right now there is a circuitous hack to force an update. I don't really want to make the TJSDocument store update mechanism public. I can work out the details of that with a code example. It involves faking a render call to the registered "app" from TJSDocument in the underlying document. The other mechanism is using stores for derived data which is not as horrible as it might sound with the writable-derived library found in TRL. Example code:
import { propertyStore } from '#runtime/svelte/store/writable-derived';

class MyActor extends Actor
{
// For typing consider creating a class that contains the derived data.
#derived = { value1: 0 };

/** @type {ActorStores} */
@stores;

constructor()
{
this.#stores = {
value1: propertyStore(this, 'value1'),
};
}

/**
* Returns derived data stores.
* @returns {ActorStores} Derived data stores.
*/
get stores() { return this.#stores; }

/**
* @returns {number} Value 1
*/
get value1() { return this.#derived.value1; }

/**
* @param {number} newValue New value 1.
*/
set value1(newValue) { this.#stores.value1.set(newValue); }
}

/**
* @typedef ActorStores
*
* @property {import('svelte/store').Writable<number>} Value 1
*/
import { propertyStore } from '#runtime/svelte/store/writable-derived';

class MyActor extends Actor
{
// For typing consider creating a class that contains the derived data.
#derived = { value1: 0 };

/** @type {ActorStores} */
@stores;

constructor()
{
this.#stores = {
value1: propertyStore(this, 'value1'),
};
}

/**
* Returns derived data stores.
* @returns {ActorStores} Derived data stores.
*/
get stores() { return this.#stores; }

/**
* @returns {number} Value 1
*/
get value1() { return this.#derived.value1; }

/**
* @param {number} newValue New value 1.
*/
set value1(newValue) { this.#stores.value1.set(newValue); }
}

/**
* @typedef ActorStores
*
* @property {import('svelte/store').Writable<number>} Value 1
*/
You'll notice if you have been following Svelte 5 and Runes that this setup is very similar except the getters for derived data are not reactive and in your Svelte components you'll have to retrieve the derived store vs usage of accessors. This pattern is nice though because when Svelte 5 is available you can replace the usage of writable-derived with $state.
<script>
import { getContext } from 'svelte';

// 'actor' is a TJSDocument.
const actor = getContext('actor');

// Value 1 is a store.
let value1 = $actor.stores.value1;
</script>

Value 1: {$value1}
<script>
import { getContext } from 'svelte';

// 'actor' is a TJSDocument.
const actor = getContext('actor');

// Value 1 is a store.
let value1 = $actor.stores.value1;
</script>

Value 1: {$value1}
I have tested things out in Svelte 5 and it's less boilerplate, but still a bit circuitous due to $state only working for local variable declarations. The goal of this Svelte 5 demo is to be able to define explicit getters / setters on a class vs dynamically defined for types / documentation and such. Svelte 5 example: https://svelte-5-preview.vercel.app/#H4sIAAAAAAAAE32S626DMAyFX8XKJhW2iq5_6UWqtLcY-0HB7dJBghLTbUJ59-VCuWjVKqHi5Hz2ORYdO_EKNUvfOibyGlnKDk3Dlox-GlfoK1aEttayVYU72epC8Yb2mciI141UBB0cCpIKDJyUrGGRrHydXPRiY2VWWEihCXKv2oHAr0BEsb3frsaOYntsiaQAKdKi4sXnroti2O0DmlzzqsW17TArn2ENxvvxiE6hm94bWM0OXmV7rNC4wWHY3sarZclPHEuWkmrRLMdtOHDcx0VPd4HfPn9R5Vr3O-icj4cSFb9iOU2v2pA4KDKqkGDI86gpJ4xe3D7A_obLYNYp-pZRzzz1cOxnOIY-uE5uky3Rz8noPEyK4g4UUqtEf7ABs7zJ9Cjzf9bpaNC_TNXnuUOXa9Y6HFsiAMbZJOOcumdqaQRnCZLBYQh4z949YGI2gP84vcNPfJu_38W7-QXn2MclMQMAAA== I wonder if any advances in the compiler can occur to support explicit accessor definition instead of this middleman / double definition approach. It's less code than the example provided above and removes the annoyance of having to work with stores. The svelte component is nicer though:
<script>
import { getContext } from 'svelte';

// 'actor' is a TJSDocument.
const actor = getContext('actor');
</script>

Value 1: {$actor.value1}
<script>
import { getContext } from 'svelte';

// 'actor' is a TJSDocument.
const actor = getContext('actor');
</script>

Value 1: {$actor.value1}
W
Wasp188d ago
Thanks guys, stores ended up being the best way to do it 😄
TM
TyphonJS (Michael)187d ago
Just remember to stay away from the get function found in svelte/store as it is not performant.
W
Wasp187d ago
Hm, I've set it up like you've suggested in Svelte 4, and if I do the following in my items;
console.log(this.#stores.bonus)
this.#stores.bonus.set(this.getBonus());
console.log(this.#stores.bonus)
this.#stores.bonus.set(this.getBonus());
The console.log provides what I expect; {subscribe: ƒ, set: ƒ, update: ƒ}, but the set throws store.subscribe is not a function? ThonkSpin specifically: https://github.com/Haxxer/FoundryVTT-Leobrew/blob/New_Leobrew/src/documents/item/item.js#L52-L53
TypeError: Failed data preparation for Actor.2qJ5YYtsYFtaJRVz.Item.q9dDkAZpdeKhTkPi. store.subscribe is not a function
at subscribe (utils.js:139:22)
at index.js:158:4
at Array.map (<anonymous>)
at index.js:157:38
at Object.subscribe2 [as subscribe] (index.js:82:11)
at update (index.js:68:34)
at Object.set (index.js:91:16)
at set bonus [as bonus] (item.js?t=1698093657038:32:22)
at LeobrewItem._prepareDerivedBonuses (item.js?t=1698093657038:52:15)
at LeobrewItem.prepareDerivedData (item.js?t=1698093657038:46:8)
TypeError: Failed data preparation for Actor.2qJ5YYtsYFtaJRVz.Item.q9dDkAZpdeKhTkPi. store.subscribe is not a function
at subscribe (utils.js:139:22)
at index.js:158:4
at Array.map (<anonymous>)
at index.js:157:38
at Object.subscribe2 [as subscribe] (index.js:82:11)
at update (index.js:68:34)
at Object.set (index.js:91:16)
at set bonus [as bonus] (item.js?t=1698093657038:32:22)
at LeobrewItem._prepareDerivedBonuses (item.js?t=1698093657038:52:15)
at LeobrewItem.prepareDerivedData (item.js?t=1698093657038:46:8)
TM
TyphonJS (Michael)187d ago
I did post somewhat pseudocode which could be the problem. I was basing it on TJSPosition: https://github.com/typhonjs-svelte/runtime-base/blob/main/src/svelte/store/position/TJSPosition.js. This is a complex custom store w/ children stores, so it may not be the easiest to follow. It is a pattern that I use in several other areas though. Perhaps run your code w/ a production build and grab the stack trace from the browser which should point to the files / source in a likely more clear stack trace. In the stack trace above all of the references to index.js I assume is the bundled output. The at Array.map (<anonymous>) line is somewhat suspect, but not sure where it is located presently. I'll also come up with a Svelte REPL example that can be confirmed and show this general pattern independently.
W
Wasp187d ago
Hm, it seems that when I set the propertyStores, the underlying subscription method treats the item itself as a store? I'm a bit confused, heh
No description
No description
W
Wasp187d ago
I suppose that my item class would need a subscribe method, lest it won't know how to handle it
TM
TyphonJS (Michael)187d ago
I don't believe you can pass multiple callbacks ala ...callbacks though that is probably not the issue you are tracking down.
W
Wasp187d ago
I... am not I am simply calling set() on the propertyStore defined as propertyStore(this, "bonus"), like your example Once it is set, that underlying svelte method is run, treating the item itself as the store, for some reason. I'm not calling subscribe() myself
TM
TyphonJS (Michael)187d ago
Oops... Yeah... Re the propertyStore thing... Definitely brain farted there. TJSPosition which I was grabbing the pseudocode for is a store w/ the child stores. You can still use this approach though and I do in other areas slightly differently. Check out SvelteReactive: https://github.com/typhonjs-fvtt-lib/svelte/blob/main/src/application/internal/state-reactive/SvelteReactive.js#L576-L604 For the actor / items you'd change things to:
constructor(...args) {
super(...args);

const derivedWritable = writable(this.#derived);
this.#stores = {
bonus: propertyStore(derivedWritable, "bonus"),
subSkills: propertyStore(derivedWritable, "subSkills"),
};
}
constructor(...args) {
super(...args);

const derivedWritable = writable(this.#derived);
this.#stores = {
bonus: propertyStore(derivedWritable, "bonus"),
subSkills: propertyStore(derivedWritable, "subSkills"),
};
}
This general approach allows you to store the data in an organized manner / efficient access for the getter accessors and still benefit from creating the stores for reactive access.
W
Wasp187d ago
Yeah, I delved into the TJSPosition and saw the discrepancies, hence my confusion! Thanks for clearing that up 😄 Ayo, great success!
TM
TyphonJS (Michael)187d ago
Awesome... So everything is working well I take it. 😄 The nice thing about the getter for stores is that you can use destructuring to access them in one statement.
W
Wasp187d ago
chefskiss
W
Wasp187d ago
I lurv myself TJSDocument & reactive stores; setting up a chain of sheet rerenders just to get the skill list to refresh when external documents change would have been a nightmare
TM
TyphonJS (Michael)187d ago
Right on... I can't wait to get the other half of TJSDocument complete especially w/ the Svelte 5 angles to it. Just have to try and convince kgar to give things a look. I'm making a new TRL release soon that thoroughly handles PopOut / separate browser window for existing components / focus management / browser access (clipboard / eye dropper) w/ reactive monitoring of the active window possible that is easy to access. It would be not so great to try and custom code a bunch of stuff for all of that.
Want results from more Discord servers?
Add your server
More Posts
Local Development w/ NPM LinkFrom @Gerark: > Hello! this is probably a base webdev question but I can't find a good reference forIssues Configuring TyphonJS in TSI'm receiving this message in my TS Files: `Cannot find module '#runtime/svelte/application' or its TRL / Svelte based game systemsThis post is a list of repos for game systems built with TRL / Svelte for Foundry. Please DM me if yList of TjsdocumentsLink to original response: https://discord.com/channels/737953117999726592/1067337319041998869/11562Is there a way to export the quest log?I'm trying to setup a world for the PF2E Beginner Box but right now I working in a beta testing worFabricate@MisterPotts. Just starting a forum post to keep track of the conversation. > Fabricate doesn't maSvelte 5 TJSDocument PrototypeGreets @FVTT ▹ Developer. As some of you might have seen there is a bit of paradigm shift that is Funky interaction with fokus managementTJS's Focus Management is creating fun issues for me again. focusKeep = true causes my drag and dropHow to get rid of funky Prosemirror overlapHi. I'm getting some overlap with Prosemirror (see image) any ideas how to avoid that? ``` "@tyFQL / TextEditor enrichment issueFrom @ooblekie: > can anyone give me a hand as to why the text keeps coming up as object promise inTJSDocument not working properly with Module Sub-Types (Data Models)In Foundry, modules can define a [Sub-Types](<https://foundryvtt.com/article/module-sub-types/>) usiTRL `0.1.2` - Fine Tuning releaseGreets @FVTT ▹ Developer! I have just released a fine tuning release that brings a few more featureEmbed a DropDown/MenuList made with SvelteHello again! 😄 I've been pondering creating a second module for Foundry and a few questions poppedSlide Animations for SvelteApplicationI'd like my window ( which is an EmptyApplicationShell ) to play a slide-in animation when open andBug: Multiple ProseMirror editors on same svelte component do not save properlySimple reproduction in a foundry world with at least one actor defined: ``` <svelte:options accessorSystem Works in Dev Mode but not after buildAfter updating to 0.1.1, I noticed something odd happening. The TITAN system now seems to work whenError building after updating to TRL 0.1.1Recently updated the TITAN system to TRL 0.1.1, but when I `npm run build` it errors out. Not sure wTJSApplication Character Sheet odd behavior with unlinked tokensHas anyone else experienced weird when using a TJSApplication as a character sheet with unlinked tokTJSDocumentCollection best practices?Hi! I've got a little component I'm using whose purpose is basically to just show a tiny preview of Release `TRL 0.1.1` - Patch releaseGreets @FVTT ▹ Developer! I have just released a patch / fix release that fixes a small oversight i