S
SolidJS2mo ago
gsoutz

What to do instead of loading a model with createMemo inside a createResource computation

/* solidjs view model */
function BookModel(id: string, name: string): BookModel {
const [name, set_name] = createSignal('')
const [chapters, set_chapters] = createSignal([])
const nb_chapters = createMemo(() => chapters().length)
return {nb_chapters}
}
async function db_load_book(book_id: string) {
const mock_db = {}
let e_book = mock_db.books.find
let res = BookModel(e_book.id, e_book.name)
let e_chapters = db.chapters.find
let chapters = e_chapters
res.set_chapters(chapters)
return res
}
function ViewComponent() {
let [book] = createResource(() => db_load_book('book_id1'))
return (<>
book()?.nb_chapters()
</>)
}
/* solidjs view model */
function BookModel(id: string, name: string): BookModel {
const [name, set_name] = createSignal('')
const [chapters, set_chapters] = createSignal([])
const nb_chapters = createMemo(() => chapters().length)
return {nb_chapters}
}
async function db_load_book(book_id: string) {
const mock_db = {}
let e_book = mock_db.books.find
let res = BookModel(e_book.id, e_book.name)
let e_chapters = db.chapters.find
let chapters = e_chapters
res.set_chapters(chapters)
return res
}
function ViewComponent() {
let [book] = createResource(() => db_load_book('book_id1'))
return (<>
book()?.nb_chapters()
</>)
}
I have 2 models, 1 database model prefixed with Entity, 1 view model that contains computations like createMemo or createEffect and signals.
The thing is, when I try to construct my view model from the database models I get from the database, that happens inside the async function within createResource calculation.
Obviously I get the warning:
computations created outside a `createRoot` or `render` will never be disposed

computations created outside a `createRoot` or `render` will never be disposed

In the code above, I explained how my code is structured in my project. But obviously there is a flaw with how I do things. Here's what I think are my options which both I think are not ideal: Option 1. Create another model that loads chapters of the book into it, and convert that into view model inside view components that render. Option 2. Write explicit createRoot code that disposes these computations somehow. I have a bunch of models that are written like this, so it will take a while for me to change them so I need a best way to do this. The main culprit is when the database loads chapters they are not embedded inside the book model immediately I need to load the book model than chapters that belong to the book. PS. This is a local only project I use dexie library to interface with the IndexedDB in the browser.
6 Replies
gsoutz
gsoutzOP2mo ago
Solid.js Playground doesn't work at the moment so I post the gist here: https://gist.github.com/eguneys/5c34047a0b68d19afe9c7b36f0a93eff
Gist
solid.js
GitHub Gist: instantly share code, notes, and snippets.
gsoutz
gsoutzOP2mo ago
Here I created a playground, but I don't understand it is not giving the warning here. https://playground.solidjs.com/anonymous/958d99b1-8bf7-4e99-9918-19d396435658
Solid Playground
Quickly discover what the solid compiler will generate from your JSX template
peerreynders
peerreynders2mo ago
I don't understand it is not giving the warning here.
The playground uses production modules. To get warnings you need to append ?dev to the import map entries—though in this case the warnings won't appear anyway. However the issue is that the linter cannot tell whether the function will only ever be used under a reactive scope where the memo can attach itself to an owner so it can be properly disposed of (otherwise leaking memory).
But obviously there is a flaw with how I do things.
The issue is that your implementation style is going against the grain of Solid's core vision— unidirectional data flow. As in data separate from behaviour. It was never meant to create "reactive objects"; the intention is to create a graph of unidirectional data flow to the UI. Something closer to this: https://playground.solidjs.com/anonymous/962f7030-a618-40b2-a9ec-a0e0c115791e
Solid Playground
Quickly discover what the solid compiler will generate from your JSX template
gsoutz
gsoutzOP2mo ago
As in data separate from behaviour. It was never meant to create "reactive objects"
I did some research, and stumbled upon this: https://github.com/solidjs/solid-realworld/blob/main/src/store/createAuth.js What is the difference between this code and my code. What is wrong with putting setters and getters inside the same "array" or an "object".
GitHub
solid-realworld/src/store/createAuth.js at main · solidjs/solid-rea...
A Solid Implementation of the Realworld Example App - solidjs/solid-realworld
peerreynders
peerreynders2mo ago
What is the difference between this code and my code.
There are two separate things happening inside createAuth. - it returns a currentUser accessor and - it augments the actions object that is passed into it. So - accessor→ read - actions → write segregation—there are no getters and setters on the same object. Ultimately it's a support function to compose this:
store = [state, actions],
store = [state, actions],
People often think that Solid's reactive primitives like
const [state, actions] = createResource(source, fetcher);
const [state, actions] = createResource(source, fetcher);
use the return state, actions tuple to copy React. That was never the case. It was to drive home [read, write] segregation in support of unidirectional data flow. This is related to the idea of Command Query Separation:
The really valuable idea in this principle is that it's extremely handy if you can clearly separate methods that change state from those that don't. This is because you can use queries in many situations with much more confidence, introducing them anywhere, changing their order. You have to be more careful with modifiers.
The context value [state, actions] retains the idiomatic [read, write] split at the top level. While state grants you fine-grained, reactive access to any of the data you can't modify state that way.
GitHub
solid-realworld/src/store/index.js at f6e77ecd652bf32f0dc9238f29131...
A Solid Implementation of the Realworld Example App - solidjs/solid-realworld
GitHub
solid-realworld/src/store/createAuth.js at f6e77ecd652bf32f0dc9238f...
A Solid Implementation of the Realworld Example App - solidjs/solid-realworld
peerreynders
peerreynders2mo ago
Mutation is only enabled from the top level commands that are made available via actions which can be highly restrictive. And even though all actions are available to anyone with access to the context, it is much easier to trace “who's responsible for this state” as all mutations have to go through a fairly restrictive set of command functions. I also see these command functions as being in the spirit of “Tell, don't Ask”; rather than just grabbing a hold of any fine-grained data/object and mutating it with impunity, using a dedicated command is “telling” (suggesting, really) the steward of the data what change is necessary (the steward having final authority on if and how that change will be implemented). So the difference really is that getters and setters are always separate and setters only really exist on the primitive level. Mutations are only exposed as action commands at the top level, never below that, in order to make it easier to preserve unidirectional data flow.
Jessica Joy Kerr (@jessitron) on X
GOTO was evil because we asked, "how did I get to this point of execution?" Mutability leaves us with, "how did I get to this state?"
X

Did you find this page helpful?