solid-router causes error: "cleanups created outside a `createRoot` or `render` will never be run"
Hi I am relatively new to solid so I could be making a simple mistake. I recently added solid router to my solid.js project and I am using the config based approach. Since I added the router and using it I get multiple errors like this:
- cleanups created outside a
createRoot
or render
will never be run
- computations created outside a createRoot
or render
will never be disposed
Since the router is actually rendered inside the render()
method of solid, I am not sure what I am doing wrong, any help how to fix the warnings would be appreciated. Below you can see my code (I also uploaded the full files because pasting entire code is higher than the discord message limit.
index.tsx
App.tsx
13 Replies
Mounting is a synchronous operation (i.e. the component DOM and it's reactive context has now been connected to the live DOM).
Because you are using an
async
function by the time onCleanup
is reached the original runtime context that created the promise is already gone which includes the owner
of the reactive context against which the clean up would be registered.
Also I'm getting the feeling that type of code should really exist in the Router's root layout, perhaps in a context or handled via action/query.
Is there a particular example that you are trying to port / emulate? (like this)
Disclaimer: I know nothing of Supabase.You are using
Note:
It already gives you a context which you can use which does most of the work for you.
(Personally I'd replace it with my own context as this one doesn't expose the
supabaseClient
via the context value which is a pain because Auth
requires a supabaseClient
prop).
Now put Auth.UserContextProvider
at the root of MainLayout
. That way any nested component can use const auth = Auth.useUser()
to get access to the auth.user
and auth.session
signals.
Now given how their Auth
component works the following isn't ideal from the solid-router perspective (where you would have a login action that throws a redirect("/")
after a successful login and throw redirect("/login")
in a logout action).
Layouts get RouteSectionProps and you want the props.location.GitHub
auth-ui/packages/solid/src/components/Auth/UserContext.tsx at 5ccb3...
Pre-built Auth UI for React. Contribute to supabase-community/auth-ui development by creating an account on GitHub.
GitHub
auth-ui/packages/solid/src/components/Auth/Auth.tsx at 5ccb32c38624...
Pre-built Auth UI for React. Contribute to supabase-community/auth-ui development by creating an account on GitHub.
GitHub
solid-router/src/types.ts at 3c214ce2ceb9b7d9d39d143229a8c6145e83e6...
A universal router for Solid inspired by Ember and React Router - solidjs/solid-router
In
MainLayout
:
useNavigate()
(Note: You may need to split MainLayout
into two parts where the nested component makes use of the Auth.useUser()
hook while the container sets the provider.)
Create a '/login' route for the Auth
component. And there:
Now you could get fancy and support a props.params.redirectTo
(params
being included in the RouteSectionProps
) encoded query parameter in your login route (set by MainLayout
on navigate
).
If you place
at the top of the Router
root layout you can use const client = useClient()
in the /login
route for the Auth
component.@peerreynders First of all I really want toto thank you for the detailed answer and explanations that is very helpful!
Exactly I was trying to do the basic example for react supabase auth with solid. I know react quite well and there I could write a Celan up function in an effect. But I now understand that in solid I cannot do that because of reactive context gets breaken when using async. Is there a page in the docs or a good yt video or something that explains the reactive context? I understand signals and reactivity but the reactive context specific to solid I think I need to read up on. I fixed it by ensuring the cleanup is created before reactive context is gone because of await and now the error is gone see my new MainLayout.tsx
But I still have below error for a different part of the code, and before I added the router I did not have this issue: createLocalStorageStore.tsx:21 computations created outside a
createRoot
or render
will never be disposed I have this custom hook createLlocalStorageStore, and before I added the router it worked fine. Now it still works but I get this, why? I know I probably also could somehow solve this with a context, but since it is a single hook and I know it will always only be used in one component, I read that you can just create a store outside a component in a file ts without having to use a context etc that this is fine in solidjs. But why does the warning now show up after adding the router or how would I fix the warning? The relevant files () are attached.
Also ofc you are right that normally such code would go into a context but since I will put the login as the main page and use a redirect and not need to have access in my whole app to the user I decided to to it simple as POC directly without context, since with supabase you can get the user anyway anywhere in the app via supabase.auth.getSession()
but ofc course if it grows etc then a context would make sense so thanks for the guidance.createLocalStorageStore.tsx:21 computations created outside aThe problem is here This code is run when the module is imported which means it's not running under a reactive context and that's a problem for thecreateRoot
orrender
will never be disposed
createEffect
inside the hook as it cannot register it's cleanup anywhere.
createLocalStorageStore
is fine inside a component setup (in a layout to be passed via props
or provider to be passed via context
) because component setup functions run within the reactive context created by render
at the root of the reactive graph.
In simple terms internally createEffect
runs getOwner
and is unhappy when it doesn't get one.“SolidJS is a state library that happens to render”. Compare that to React which is only interested in state inasfar as it relates to rendering; that's an entirely different perspective.
So while in Solid state doesn't have to be monolithic, to maintain its reactivity its supporting reactive graph has to grow from the root established by the
render
function.
Solid's compiler is largely concerned with transforming JSX. Svelte uses their compiler to manage reactivity; that's not the case with Solid; its reactivity is managed at runtime, starting from render
and growing from there.
That's why in Solid props
are reactive. The container component passes a reactive dependency to a nested component; that way when that dependency changes that change can propagate into the reactive subgraph that the nested component created at setup time.Ryan Carniato
YouTube
Components Are Pure Overhead
With many starting to look at incorporating Reactivity into existing Frameworks I wanted to take a moment to look at how Frameworks evolved into using Components, the evolution of change and state management, to best understand the impact of where things are going.
[0:00] Preamble
[12:02] Change Management
[40:51] MVC to Components
[1:12:34] Th...
since with supabase you can get the user anyway anywhere in the app via supabase.auth.getSession()
Stop right there.
Retrieve a session
That's an async
function. Now that you are using the @solidjs/router
async values should be wrapped in createAsync
. createAsync
acts as a “port” for updates of a (settling) async value into the reactive realm (which is really synchronous, not in a fake synchronous await
kind of way).
Solid 2.x will integrate async in a slightly different way but for the time being async has no business inside the reactive graph; async
uses createAsync
as an adapter to interact with the reactive graph.
what color is your function
In a reactive world you don't tend to (imperatively) get something when you need it but instead you try to have it ready by the time you need it; or more to the point: things change either because of your actions or external events and that change propagates to adjust all the things the depend on it.
Furthermore supabase ideally wants you to Listen to auth events and that is exactly how their context works. getSession
is only used to get the initial value, past that events update the session
and user
signals.JavaScript: Retrieve a session | Supabase Docs
Supabase API reference for JavaScript: Retrieve a session
JavaScript: Listen to auth events | Supabase Docs
Supabase API reference for JavaScript: Listen to auth events
As Mark Erikson already stated years ago Context is a form of Dependency Injection
And Solid's context is even simpler, it's not reactive. The context value simply holds things that are already reactive.
For example this store is completely unnecessary (the fact that
setValue
isn't used anywhere is the give away).
A simple object literal would be enough here as the session
and user
accessor's are already reactive.
So I would argue that context is the natural way of injecting dependencies (like user
, session
, gameState
) that need to be accessible at arbitrary points within the reactive graph.
Now, if it is just a container passing a dependency to a nested component then props
is good enough.Mark's Dev Blog
Blogged Answers: Why React Context is Not a "State Management" Tool...
Definitive answers and clarification on the purpose and use cases for Context and Redux
GitHub
auth-ui/packages/solid/src/components/Auth/UserContext.tsx at 5ccb3...
Pre-built Auth UI for React. Contribute to supabase-community/auth-ui development by creating an account on GitHub.
The only other way to sanely integrate async server state is
query
while using action
to update that server state.
One query
can be shared by multiple createAsync
which will all be updated when that one query
is updated.
https://discord.com/channels/722131463138705510/1330302780250001449/1330366296956735582
But the supabase client API seems to favour events and for that context + signals works better.Is there a page in the docs or a good yt video or something that explains the reactive context?I find that Building a Reactive Library from Scratch is at the core of understanding Solid. In particular and later Note the
context.push(running);
before the effect function is run; without that context
variable, subscriptions necessary for change propagation cannot be managed. This is an oversimplified demonstration of why an owner is necessary.
In reality of course a reactive root is responsible for disposing of all the reactive resources that were created under it.DEV Community
Building a Reactive Library from Scratch
In the previous article A Hands-on Introduction to Fine-Grained Reactivity I explain the concepts...
What the article doesn't highlight is that
createEffect
should only be used when you are leaving the reactive graph.
So using createEffect
to subscribe to one reactive dependency to set another signal/store is usually a mistake (there are rare exceptions that Solid 2.x will mostly eliminate) and usually pave the road to infinite reactive loops.
For the reactive graph to operate efficiently consumers need to be directly subscribed to their dependencies, createEffect
breaks the direct connection within the reactive graph between the dependency and the consumer; given that effects don't run until the values in the reactive graph are stable, effects that set reactive sources start updating the reactive graph all over again.
Ideally effects only adjust the outside world to the current state of the reactive graph.GitHub
strello/src/components/Board.tsx at 9c9ae973d96cc045914e696757a1b5f...
Contribute to solidjs-community/strello development by creating an account on GitHub.
For cases like optimistic updates you can use
createWritableMemo
. The concept here is that the dependencies inside the effect function do have the ultimate authority on the value exposed by the memo.
But here you can use the setter to temporarily override the memo value which will remain until the dependencies update again.
So for an optimistic update you provide the memo's value “optimistically” so that by the time the real dependencies catch up, the update work has already been done.
Afaik that's how signals in 2.x will work anyway.Solid Primitives
A library of high-quality primitives that extend SolidJS reactivity