S
SolidJS•2w ago
hcker2000

Error when trying to use provider method

I have the following method setup in my /src/quickPlay/provider.tsx file:
const stopAllSounds = () => {
store.sounds.forEach((sound, index) => {
setStore("sounds", index, "status", "stopped")
stopSound(sound.id)
})
}
const stopAllSounds = () => {
store.sounds.forEach((sound, index) => {
setStore("sounds", index, "status", "stopped")
stopSound(sound.id)
})
}
The providers export looks like this:
export const [QuickSoundProvider, useStore] = createContextProvider(() => {
return {
store: store,
setStore: setStore,
addSound: () => addSound(),
removeSound: (soundId) => removeSound(soundId),
playSound: (soundId) => playSound(soundId),
stopAllSounds: () => stopAllSounds(),
setSoundVolume: (soundId, event) => setSoundVolume(soundId, event),
setSearch: (value) => setSearch(value),
getSounds: () => getSounds()
}
})
export const [QuickSoundProvider, useStore] = createContextProvider(() => {
return {
store: store,
setStore: setStore,
addSound: () => addSound(),
removeSound: (soundId) => removeSound(soundId),
playSound: (soundId) => playSound(soundId),
stopAllSounds: () => stopAllSounds(),
setSoundVolume: (soundId, event) => setSoundVolume(soundId, event),
setSearch: (value) => setSearch(value),
getSounds: () => getSounds()
}
})
35 Replies
hcker2000
hcker2000OP•2w ago
I import this into my main layout file /src/layout.tsx and try to use it in the beforeunload event:
import { QuickPlay } from './quickPlay/view'
import { Scene } from './scene/scene'
import { QuickSoundProvider, useStore } from './quickPlay/provider'
import svgIconUrl from '../assets/icon.svg'
import { onMount } from 'solid-js'

export function Layout() {

onMount(() => {
const splashScreen = document.getElementsByClassName('splash')[0]

setTimeout(() => {
splashScreen.classList.add('fade-out')
setTimeout(() => {
splashScreen.classList.add('d-none')
}, 2000)
}, 2000)

// window.electronAPI.onAppBeforeQuit(() => {
// alert('test');
// console.log('Application is about to quit.')
// const { stopAllSounds } = useStore()
// stopAllSounds()
// })

window.addEventListener('beforeunload', (event) => {
event.preventDefault()
const { stopAllSounds } = useStore() // ! errors with TypeError: Cannot destructure property 'stopAllSounds' of 'useStore(...)' as it is undefined.
stopAllSounds()
console.log('I do not want to be closed')

// Unlike usual browsers that a message box will be prompted to users, returning
// a non-void value will silently cancel the close.
// It is recommended to use the dialog API to let the user confirm closing the
// application.
e.returnValue = true
})
})
import { QuickPlay } from './quickPlay/view'
import { Scene } from './scene/scene'
import { QuickSoundProvider, useStore } from './quickPlay/provider'
import svgIconUrl from '../assets/icon.svg'
import { onMount } from 'solid-js'

export function Layout() {

onMount(() => {
const splashScreen = document.getElementsByClassName('splash')[0]

setTimeout(() => {
splashScreen.classList.add('fade-out')
setTimeout(() => {
splashScreen.classList.add('d-none')
}, 2000)
}, 2000)

// window.electronAPI.onAppBeforeQuit(() => {
// alert('test');
// console.log('Application is about to quit.')
// const { stopAllSounds } = useStore()
// stopAllSounds()
// })

window.addEventListener('beforeunload', (event) => {
event.preventDefault()
const { stopAllSounds } = useStore() // ! errors with TypeError: Cannot destructure property 'stopAllSounds' of 'useStore(...)' as it is undefined.
stopAllSounds()
console.log('I do not want to be closed')

// Unlike usual browsers that a message box will be prompted to users, returning
// a non-void value will silently cancel the close.
// It is recommended to use the dialog API to let the user confirm closing the
// application.
e.returnValue = true
})
})
I get this error when closing the window:
TypeError: Cannot destructure property 'stopAllSounds' of 'useStore(...)' as it is undefined.
at layout.tsx:28:21
TypeError: Cannot destructure property 'stopAllSounds' of 'useStore(...)' as it is undefined.
at layout.tsx:28:21
bigmistqke
bigmistqke•2w ago
Doing useContext in an event handler is not going to work Context only works during mount A but it has default values 🤔 Is it the intention to just use it as a singleton? Like you never override its default values?
hcker2000
hcker2000OP•2w ago
No the store changes alot For example when it's playing the sound the specifics sounds status property is updated to playing @bigmistqke I am attempting to set all the sound objects status property to stopped when the window is closed so when it's loaded next it won't think the sounds are playing when they are actually stopped
bigmistqke
bigmistqke•2w ago
a my bad i was misinterpreting how createContextProvider works you can not get context in event handlers that's one of the limitations of context: it does not work behind any async barrier
createEffect(async () => {
await new Promise((resolve) => setTimeout(resolve, 1000));
const context = useContext()
})
createEffect(async () => {
await new Promise((resolve) => setTimeout(resolve, 1000));
const context = useContext()
})
this will not work for example event handlers are similar: they run after the component is called
bigmistqke
bigmistqke•2w ago
Solid Playground
Quickly discover what the solid compiler will generate from your JSX template
hcker2000
hcker2000OP•2w ago
Interesting, I'll take a look at that example playground
bigmistqke
bigmistqke•2w ago
Context is implemented very similarly to how https://youtu.be/cELFZQAMdhQ?t=167&si=YG13wu48jvhgCZJt works. It's like an array to which is pushed before executing the children, and then popped after. So that's why accessing context after this async barriers don't work.
SolidJS
YouTube
Intro to SolidJS reactivity (in 5 minutes)
An introduction video that walks you through Solid's reactivity in under 5 minutes.
bigmistqke
bigmistqke•2w ago
Can mb give an intuition
hcker2000
hcker2000OP•2w ago
that makes sense it has been long enough that I forgot those were even context providers. I see that in your example it logs out of order second then first. does that mean there is no way to adjust the store beforeunload?
hcker2000
hcker2000OP•2w ago
keeping in mind im working with this code https://github.com/hcker2000/ttrpg-sounds
GitHub
GitHub - hcker2000/ttrpg-sounds: A soundboard for creating scenes (...
A soundboard for creating scenes (tavern, cave, epic battle) with multiple tracks - hcker2000/ttrpg-sounds
hcker2000
hcker2000OP•2w ago
specifically this provider
hcker2000
hcker2000OP•2w ago
GitHub
ttrpg-sounds/src/renderer/src/quickPlay/provider.tsx at main · hcke...
A soundboard for creating scenes (tavern, cave, epic battle) with multiple tracks - hcker2000/ttrpg-sounds
bigmistqke
bigmistqke•2w ago
You could do
function Child(){
const context = useContext(...) window.addEventListener('beforeunload', () => console.log(context))
}
function Child(){
const context = useContext(...) window.addEventListener('beforeunload', () => console.log(context))
}
Access the context before the async boundary
hcker2000
hcker2000OP•2w ago
@bigmistqke I just gave that a try with no luck but im assuming it may be because im outside the provider
hcker2000
hcker2000OP•2w ago
No description
bigmistqke
bigmistqke•2w ago
Exactly The way how you are doing context is a bit unorthodox btw Are you sure you don't want to do a singleton instead?
hcker2000
hcker2000OP•2w ago
im adjusting now to do it inside the context i mean ideally there should only ever be one of those providers
bigmistqke
bigmistqke•2w ago
Do you want to do ssr? Because how you are handling context rn will still create issues if you would ssr it
hcker2000
hcker2000OP•2w ago
in this case probably not as this is an electron app wrapped around solid js and my understanding is electron just runs a chrome browser so moving that code into the view which is inside the provider fixes the error now i just need to figure out why its not doing what its supposed to
bigmistqke
bigmistqke•2w ago
With context the goal is often to scope things to a branch of the ui-tree But I guess this is not really the case with your setup either.
hcker2000
hcker2000OP•2w ago
yea and there is ui that goes along with this. in this case there will only ever be one provider in the tree though is there anything specifically wrong with how the provider is setup? im always game for learning
bigmistqke
bigmistqke•2w ago
What makes your setup a bit unorthodox is that it is something in between a singleton and context. It's like you are providing a singleton, but through context. So it's a bit the worst of the two worlds: you cannot ssr it (which doesn't matter in ur case) and you have to think about where you access the store/actions: not all useContexts will work, as we saw in the example of the event handler. I m personally a big fan of the singleton approach, and would only introduce the added complexity of context if I would really need it: for example if you would want to have multiple instances of the store/actions in a single page, or if u wanna ssr it.
hcker2000
hcker2000OP•2w ago
yea i have no issues with a singleton, the docs just specifically mention using the context providers to handle this. The singleton im assuming would be more vanilla js than solidjs correct? less magic i guess the only other question too is reactivity on the singleton
bigmistqke
bigmistqke•2w ago
The singleton is having a store somewhere and mb some functions to manipulate them and then you just import these functions and this store wherever you want to Sorry if that wasn't clear, is bit jargony
hcker2000
hcker2000OP•2w ago
all good, does anything that ever references a store get updated by the redering?
bigmistqke
bigmistqke•2w ago
Can you link me which part of the docs mentions this? Exactly
hcker2000
hcker2000OP•2w ago
ok then yea maybe a simple example of the singleton store would help let me see if i can find it in the docs
hcker2000
hcker2000OP•2w ago
i think maybe i saw this page https://www.solidjs.com/tutorial/stores_context and got down this track
SolidJS
Solid is a purely reactive library. It was designed from the ground up with a reactive core. It's influenced by reactive principles developed by previous libraries.
bigmistqke
bigmistqke•2w ago
It's basically the difference between import { store } from "./store" and const store = useContext() What they mean with Using Context has the benefit of being created as part of the reactive system and managed by it. is that solid automatically cleans up effects and signals that are created inside a component whenever the component unmounts. With singletons you often don't need to clean up: you want this store to be available the whole time your app is running. You might sometimes get a warning computations outside of createRoot cannot be cleaned up which refers to that. You can safely ignore those, or wrap the store and actions with a createRoot if you don't like ignoring warnings In your case the store and the actions were still created outside of components, so you might have seen that warning too.
hcker2000
hcker2000OP•3d ago
ok ill mess around and see if i can come up with a singleton store @bigmistqke finally got some time to mess around with this again. This what you were thinking?
import { createStore } from "solid-js/store";

class StoreSingleton {
// Private static instance
static #instance;

// Private constructor to prevent direct instantiation
constructor() {
if (StoreSingleton.#instance) {
throw new Error(
"Use StoreSingleton.getInstance() to get an instance of this class.",
);
}

// Initialize the Solid.js store with default state
this.state = createStore({
user: null,
todos: [],
});
}

// Public static method to get the singleton instance
static getInstance() {
if (!StoreSingleton.#instance) {
StoreSingleton.#instance = new StoreSingleton();
}
return StoreSingleton.#instance;
}

// Getter for accessing the Solid store
getState() {
return this.state[0];
}

// Methods to update the state
setUser(user) {
this.state[1]({ user });
}

addTodo(todo) {
this.state[1]("todos", (todos) => [...todos, todo]);
}

removeTodo(index) {
this.state[1]("todos", (todos) => todos.filter((_, i) => i !== index));
}
}

// Usage
const store = StoreSingleton.getInstance();
store.setUser({ id: 1, name: "John Doe" });
store.addTodo({ id: 1, text: "Learn Solid.js", completed: false });
store.addTodo({ id: 2, text: "Build a project", completed: false });

console.log(store.getState());
// Output:
// {
// user: { id: 1, name: "John Doe" },
// todos: [
// { id: 1, text: "Learn Solid.js", completed: false },
// { id: 2, text: "Build a project", completed: false }
// ]
// }

const sameStore = StoreSingleton.getInstance();
sameStore.removeTodo(0);

console.log(sameStore.getState());
// Output:
// {
// user: { id: 1, name: "John Doe" },
// todos: [
// { id: 2, text: "Build a project", completed: false }
// ]
// }
import { createStore } from "solid-js/store";

class StoreSingleton {
// Private static instance
static #instance;

// Private constructor to prevent direct instantiation
constructor() {
if (StoreSingleton.#instance) {
throw new Error(
"Use StoreSingleton.getInstance() to get an instance of this class.",
);
}

// Initialize the Solid.js store with default state
this.state = createStore({
user: null,
todos: [],
});
}

// Public static method to get the singleton instance
static getInstance() {
if (!StoreSingleton.#instance) {
StoreSingleton.#instance = new StoreSingleton();
}
return StoreSingleton.#instance;
}

// Getter for accessing the Solid store
getState() {
return this.state[0];
}

// Methods to update the state
setUser(user) {
this.state[1]({ user });
}

addTodo(todo) {
this.state[1]("todos", (todos) => [...todos, todo]);
}

removeTodo(index) {
this.state[1]("todos", (todos) => todos.filter((_, i) => i !== index));
}
}

// Usage
const store = StoreSingleton.getInstance();
store.setUser({ id: 1, name: "John Doe" });
store.addTodo({ id: 1, text: "Learn Solid.js", completed: false });
store.addTodo({ id: 2, text: "Build a project", completed: false });

console.log(store.getState());
// Output:
// {
// user: { id: 1, name: "John Doe" },
// todos: [
// { id: 1, text: "Learn Solid.js", completed: false },
// { id: 2, text: "Build a project", completed: false }
// ]
// }

const sameStore = StoreSingleton.getInstance();
sameStore.removeTodo(0);

console.log(sameStore.getState());
// Output:
// {
// user: { id: 1, name: "John Doe" },
// todos: [
// { id: 2, text: "Build a project", completed: false }
// ]
// }
Obviously not specific to my setup just yet but wanted to make sure thats the general idea before i modify my code base also wanted to get some ideas on where you might keep such a file directory structure wise
bigmistqke
bigmistqke•3d ago
this is what i was thinking: store.js
const [store, setStore] = createStore({ ... })
export function getUser(){
return store.user
}
export function setUser(user){
setStore({user})
}
export functio addTodo(todo){
setStore('todos', todos => [...todos, todo])
}
// etcetera
const [store, setStore] = createStore({ ... })
export function getUser(){
return store.user
}
export function setUser(user){
setStore({user})
}
export functio addTodo(todo){
setStore('todos', todos => [...todos, todo])
}
// etcetera
main.js
import { setUser } from "./store"
setUser({ ... })
import { setUser } from "./store"
setUser({ ... })
hcker2000
hcker2000OP•3d ago
gotch but without the constructor wont the import always get a new instance ?
bigmistqke
bigmistqke•3d ago
no! that's the beauty of modules you can scope variables that are used throughout your project just with files, you don't need additional abstractions.
bigmistqke
bigmistqke•3d ago
for example
// main.js
import "./module1"
import "./module2"

// module1.js
console.log('hallo')

// module2.js
import "./module2"
// main.js
import "./module1"
import "./module2"

// module1.js
console.log('hallo')

// module2.js
import "./module2"
is only going to log 'hallo' once: that's because the body of a module is only called once: the first time it is imported from somewhere.
Solid Playground
Quickly discover what the solid compiler will generate from your JSX template
hcker2000
hcker2000OP•3d ago
very interesting, im used to php where you have to do some work to make something a signleton. Thats really good to know and much simpler than i figured it would be. Now I will start messing around with them in my app
Want results from more Discord servers?
Add your server