qwik
qwik copied to clipboard
Need an easy solution to share a context with slotted elements
Is your feature request related to a problem?
I'm trying to create a set of components that could be used as following:
<Dropdown label="Animals">
<MenuItem>Cat</MenuItem>
<MenuItem>Dog</MenuItem>
</Dropdown>
The MenuItem
elements will be in a slot of the parent Dropdown
component. They need to communicate to the parent, whenever they are clicked, in order to have only one of them selected at a time.
So I need some kind of dynamic context that is created for each instance of a Dropdown
element, that will be shared with the MenuItem
elements.
Today, the only solution to perform that is to create a new context outside of the Dropdown
element, and give it as property to the Dropdown
, and the MenuItem
elements.
const dropdownContext = createContext('dropdown');
<Dropdown label="Animals" context={dropdownContext}>
<MenuItem context={dropdownContext}>Cat</MenuItem>
<MenuItem context={dropdownContext}>Dog</MenuItem>
</Dropdown>
Each MenuItem
would use a callback from the context to inform the Dropdown
about the click event. And the Dropdown
would inform the MenuItem
s about any change if the need to unselect themselves.
The drawbacks of this solution is that I need to create manually a new context each time I create a new Dropdown element.
Describe the solution you'd like
I would like an easy way to create a context inside a parent element, and share it with slotted elements. A possible sollution would to allow slotted elements retreive a context from a parent, using the context name (currently, the only way for a child to retreive a context, is using a reference).
Describe alternatives you've considered
Any other solution for communication between parent and slotted elements is acceptable for me.
Additional context
No response
why every Dropdown needs to create a new context?
the parent should use: useContextProvider()
to pass a shared store and, the MenuItem use useContext()
to get a holding of that store.
both using the same globally declared createContext()
I've tried the following using a globally declared createContext():
export const Dropdown = component$((props: DropdownProps) => {
const store = useStore<DropdownStore>({
open: false,
selected: props.selected,
onMenuItemClicked$: async (name: string) => {
console.log(`${name} clicked`);
store.selected = name;
},
});
useContextProvider(DropdownContext, store);
But actually, this doesn't work, because the callback can't be serialized. So, I don't find any solution for a slotted element, to communicate with a parent.
Any suggestion ?
Try this:
import {$} from '@builder.io/qwik';
const store = useStore<DropdownStore>({
open: false,
selected: props.selected,
onMenuItemClicked: $(async (name: string) => {
console.log(`${name} clicked`);
store.selected = name;
}),
});
Didn't work... I've got the following error:
2:09:14 PM [vite] Internal server error: Cannot access 'store' before initialization
The problem occurs because of the line store.selected = name;
This should be fixed by #1985.
I don't understand if this is fixed and in the actual (0.16.1) package. Following the example of @manucorporat I have the same error of @emexal-company
[vite] Internal server error: Cannot access 'store' before initialization
Actually, this is solved. The problem is that I was trying to use the variable store
whereas I was initializing it.
The final solution that I used is :
- Create the store without the event handler
const store = useStore<DropdownStore>({
open: false,
selected: props.selected,
});
- Create the event handler
const onMenuItemClicked$ = $((value: string) => {
console.log(`${value} clicked`);
store.selected = value;
});
- Add the handler to the store (Put it inside useWatch, otherwise you will see a warning)
useWatch$(() => {
store.onMenuItemClicked$ = onMenuItemClicked$;
});
I ended up making something like you suggested, moving the event handler outside the store, and referencing them inside. No need to use useWatch$, which is deprecated
This is fixed!