apademide
apademide
SSolidJS
Created by apademide on 12/6/2023 in #support
useContext() from custom element
I also took the opportunity to slightly improve the types inference and provide the same options as component-register's compose method with the simplicity of use of the register method
type MyProps = {
props: string
}

register("my-component",
{
props: "definition"
} as MyProps,
{
// Option object that is passed directly to component-register's register function
// so it has the same options, with the addition of the `with` option:
with: [
// List of functions that would have been passed to compose() otherwise
// withSolid included by default
(comp) => comp
]
})((props, { element }) => {
// `props` inferred as MyProps automatically

element.addPropertyChangedCallback(() => { })
element.addReleaseCallback(() => { })
element.lookupProp("foo")
element.props
element.renderRoot

return (
<div>
My Component
</div>
);
});
type MyProps = {
props: string
}

register("my-component",
{
props: "definition"
} as MyProps,
{
// Option object that is passed directly to component-register's register function
// so it has the same options, with the addition of the `with` option:
with: [
// List of functions that would have been passed to compose() otherwise
// withSolid included by default
(comp) => comp
]
})((props, { element }) => {
// `props` inferred as MyProps automatically

element.addPropertyChangedCallback(() => { })
element.addReleaseCallback(() => { })
element.lookupProp("foo")
element.props
element.renderRoot

return (
<div>
My Component
</div>
);
});
15 replies
SSolidJS
Created by apademide on 12/6/2023 in #support
useContext() from custom element
For the final solution I came with something that works quite well I made that wrapper around the compose + register methods (which passes down all logic to the default function, so it has the same features + automatically injects withSolid for convenience + pushes tags to registeredComponents)
import {
register as componentRegister,
compose,
type ComponentType,
type RegisterOptions,
type PropsDefinitionInput,
} from "component-register";
import { withSolid } from "solid-element";

type TRegister = <T>(
tag: string,
props: PropsDefinitionInput<T>,
options?: RegisterOptions & {
with?: ((C: ComponentType<any>) => ComponentType<any>)[];
},
) => (ComponentType: ComponentType<T>) => any;

const registeredComponents: string[] = [];

const register: TRegister = (tag, props, options) => {
if (!tag || !tag.includes("-")) {
throw new TypeError(
`Web component tag "${tag}" is invalid. It must be a string containing a dash.`,
);
}

if (!registeredComponents.includes(tag)) {
registeredComponents.push(tag);
} else if (process.env.NODE_ENV !== "production") {
console.warn(`Web component "${tag}" has been registered multiple times.`);
}

return compose(
componentRegister(tag, props, options),
...(() => {
if (!options?.with) return [withSolid];
if (options.with.includes(withSolid)) return options.with;
return [withSolid, ...options.with];
})(),
);
};

export { register, registeredComponents };
import {
register as componentRegister,
compose,
type ComponentType,
type RegisterOptions,
type PropsDefinitionInput,
} from "component-register";
import { withSolid } from "solid-element";

type TRegister = <T>(
tag: string,
props: PropsDefinitionInput<T>,
options?: RegisterOptions & {
with?: ((C: ComponentType<any>) => ComponentType<any>)[];
},
) => (ComponentType: ComponentType<T>) => any;

const registeredComponents: string[] = [];

const register: TRegister = (tag, props, options) => {
if (!tag || !tag.includes("-")) {
throw new TypeError(
`Web component tag "${tag}" is invalid. It must be a string containing a dash.`,
);
}

if (!registeredComponents.includes(tag)) {
registeredComponents.push(tag);
} else if (process.env.NODE_ENV !== "production") {
console.warn(`Web component "${tag}" has been registered multiple times.`);
}

return compose(
componentRegister(tag, props, options),
...(() => {
if (!options?.with) return [withSolid];
if (options.with.includes(withSolid)) return options.with;
return [withSolid, ...options.with];
})(),
);
};

export { register, registeredComponents };
Which is then used this way in my <SafeHtml> comp
if (fragment && registeredComponents.length) {
const registeredComponentsSelector = registeredComponents.join(",");
const owner = getOwner();
const customElements = fragment.querySelectorAll(
registeredComponentsSelector,
);

for (const customElement of customElements) {
(customElement as any)._$owner = owner;
}
}
if (fragment && registeredComponents.length) {
const registeredComponentsSelector = registeredComponents.join(",");
const owner = getOwner();
const customElements = fragment.querySelectorAll(
registeredComponentsSelector,
);

for (const customElement of customElements) {
(customElement as any)._$owner = owner;
}
}
15 replies
SSolidJS
Created by apademide on 12/6/2023 in #support
useContext() from custom element
So the answer is somewhat disappointing but Ryan provided a workaround that did the trick You may find what I came around with here: https://github.com/solidjs/solid/issues/1976#issuecomment-1846801576
15 replies
SSolidJS
Created by apademide on 12/6/2023 in #support
useContext() from custom element
15 replies
SSolidJS
Created by apademide on 12/6/2023 in #support
useContext() from custom element
Ok I could setup a minimal example of the issue in both cases Will open an issue but include it here for ref too
import { render } from "solid-js/web";
import { createContext, useContext } from "solid-js";
import { customElement } from "solid-element";

const context = createContext(false);

customElement("my-component", {}, (props, { element }) => {
const ctx = useContext(context);
console.log("ctx", ctx);
return <div>Can access context: {ctx ? "yes" : "no"}</div>
});

customElement("my-provider", {}, (props, { element }) => {
return (
<context.Provider value={true}>
<slot />
</context.Provider>
);
});

function MyProvider(props: any) {
return (
<context.Provider value={true}>
{props.children}
</context.Provider>
)
}



render(
() => (<div>
<div>
Variant with a web component-based context
<div>
<my-provider>
<div innerHTML="<my-component />" />
</my-provider>
</div>
</div>

<hr />

<div>
Variant with a default Solid context
<MyProvider>
<div innerHTML="<my-component />" />
</MyProvider>
</div>

<hr />

<div>
Please note that using the web component in the JSX directly does work …
<MyProvider>
<my-component />
</MyProvider>
… with both methods
<my-provider>
<my-component />
</my-provider>
</div>
</div>),
document.getElementById("app")!,
);
import { render } from "solid-js/web";
import { createContext, useContext } from "solid-js";
import { customElement } from "solid-element";

const context = createContext(false);

customElement("my-component", {}, (props, { element }) => {
const ctx = useContext(context);
console.log("ctx", ctx);
return <div>Can access context: {ctx ? "yes" : "no"}</div>
});

customElement("my-provider", {}, (props, { element }) => {
return (
<context.Provider value={true}>
<slot />
</context.Provider>
);
});

function MyProvider(props: any) {
return (
<context.Provider value={true}>
{props.children}
</context.Provider>
)
}



render(
() => (<div>
<div>
Variant with a web component-based context
<div>
<my-provider>
<div innerHTML="<my-component />" />
</my-provider>
</div>
</div>

<hr />

<div>
Variant with a default Solid context
<MyProvider>
<div innerHTML="<my-component />" />
</MyProvider>
</div>

<hr />

<div>
Please note that using the web component in the JSX directly does work …
<MyProvider>
<my-component />
</MyProvider>
… with both methods
<my-provider>
<my-component />
</my-provider>
</div>
</div>),
document.getElementById("app")!,
);
15 replies
SSolidJS
Created by apademide on 12/6/2023 in #support
useContext() from custom element
Also, worth mentionning the User context depends on another context (defined the same way):
function createUser() {
const Navigation = useNavigation();

class User {
// A few functions using Navigation
}

return User;
}
function createUser() {
const Navigation = useNavigation();

class User {
// A few functions using Navigation
}

return User;
}
with this at the top level of the app's tree
<NavigationProvider>
<UserProvider>
{props.children}
</UserProvider>
</NavigationProvider>;
<NavigationProvider>
<UserProvider>
{props.children}
</UserProvider>
</NavigationProvider>;
15 replies
SSolidJS
Created by apademide on 12/6/2023 in #support
useContext() from custom element
Additional context With the implementation of the User class trimmed, this is the context definition
import { createContext, useContext, type JSX } from "solid-js";

type TUserProviderProps = {
children: JSX.Element;
};

const UserContext = createContext<ReturnType<typeof createUser>>();

export function UserProvider(props: TUserProviderProps) {
return (
<UserContext.Provider value={createUser(props)}>
{props.children}
</UserContext.Provider>
);
}

export function useUser() {
return useContext(UserContext);
}

function createUser(props) {
class User {

}

return User;
}
import { createContext, useContext, type JSX } from "solid-js";

type TUserProviderProps = {
children: JSX.Element;
};

const UserContext = createContext<ReturnType<typeof createUser>>();

export function UserProvider(props: TUserProviderProps) {
return (
<UserContext.Provider value={createUser(props)}>
{props.children}
</UserContext.Provider>
);
}

export function useUser() {
return useContext(UserContext);
}

function createUser(props) {
class User {

}

return User;
}
15 replies
SSolidJS
Created by apademide on 4/11/2023 in #support
frontend navigation: null is not an object (evaluating '_el$11.nextSibling')
I don't really know, I think it's a bug ?
14 replies
SSolidJS
Created by apademide on 4/11/2023 in #support
frontend navigation: null is not an object (evaluating '_el$11.nextSibling')
i believe it's related to web components handling cause of their custom names ?
14 replies
SSolidJS
Created by apademide on 4/11/2023 in #support
frontend navigation: null is not an object (evaluating '_el$11.nextSibling')
14 replies
SSolidJS
Created by apademide on 4/11/2023 in #support
frontend navigation: null is not an object (evaluating '_el$11.nextSibling')
A workaround I found is to wrap <item-name>…</item-name> in a <div>
14 replies
SSolidJS
Created by apademide on 4/11/2023 in #support
frontend navigation: null is not an object (evaluating '_el$11.nextSibling')
Bringing this post back to life as I still couldn't find a fix
14 replies
SSolidJS
Created by apademide on 4/11/2023 in #support
frontend navigation: null is not an object (evaluating '_el$11.nextSibling')
actually if you want the whole code base, here's the repo: https://github.com/AUAUST/clients-nkco-weltkern-frontend/tree/homepage the erroring component is in ./src/components/layouts/grid/Item.tsx
14 replies
SSolidJS
Created by apademide on 4/11/2023 in #support
frontend navigation: null is not an object (evaluating '_el$11.nextSibling')
I didn't manage to debug that as it only appears when using the frontend router; otherwise the component works as expected
14 replies
SSolidJS
Created by apademide on 4/11/2023 in #support
frontend navigation: null is not an object (evaluating '_el$11.nextSibling')
removing only item-pricing or RarityIcon doesn't change anything: if i have either one of them, i get the error
14 replies
SSolidJS
Created by apademide on 4/11/2023 in #support
frontend navigation: null is not an object (evaluating '_el$11.nextSibling')
item-pricing is a web component, not a Solid one i case it matters
14 replies
SSolidJS
Created by apademide on 4/11/2023 in #support
frontend navigation: null is not an object (evaluating '_el$11.nextSibling')
managed to remove the error by removing that part:
<div>
<item-pricing>
<Show when={props.price.trend && props.price.trend !== "neutral"}>
<span class={`trend ${props.price.trend}`}>
{props.price.trend === "up" ? "↑" : "↓"}&nbsp;
</span>
</Show>
<span class="price">
<span class="currency">
{formattedPrice(props.price.amount, props.price.currency)}
</span>
<small> or </small>
<span class="kernings">
{formattedKernings(props.price.kernings)}&nbsp;KS+
</span>
</span>
</item-pricing>
<RarityIcon rarity={props.rarity} />
</div>
<div>
<item-pricing>
<Show when={props.price.trend && props.price.trend !== "neutral"}>
<span class={`trend ${props.price.trend}`}>
{props.price.trend === "up" ? "↑" : "↓"}&nbsp;
</span>
</Show>
<span class="price">
<span class="currency">
{formattedPrice(props.price.amount, props.price.currency)}
</span>
<small> or </small>
<span class="kernings">
{formattedKernings(props.price.kernings)}&nbsp;KS+
</span>
</span>
</item-pricing>
<RarityIcon rarity={props.rarity} />
</div>
14 replies
SSolidJS
Created by apademide on 4/11/2023 in #support
frontend navigation: null is not an object (evaluating '_el$11.nextSibling')
this is the component's code:
/*trimmed imports cause message is too long*/

export default function Item(props: GridLayout.ItemProps) {
function formattedPrice(amount: number, currency: string) {
return new Intl.NumberFormat("fr-CH", {
style: "currency",
currency,
currencyDisplay: "narrowSymbol",
}).format(amount);
}
const kerningsFormatter = new Intl.NumberFormat("fr-CH", {
style: "decimal",
useGrouping: true,
});
function formattedKernings(kernings: number) {
return kerningsFormatter.format(kernings);
}

return (
<layout-grid-item
role="article"
attr:locked={false}
attr:type={props.type}
attr:rarity={props.rarity}
>
<A href="/hello">
<item-image>
<StatusIcon remaining={props.remaining} />

<img src={props.image} alt="" />
</item-image>

<item-description>
<item-name>
<p class="author">{props.author}</p>
<p class="title">{props.title}</p>
<p class="publisher">{props.publisher}</p>
</item-name>

<hr />
<div>
<item-pricing>
<Show when={props.price.trend && props.price.trend !== "neutral"}>
<span class={`trend ${props.price.trend}`}>
{props.price.trend === "up" ? "↑" : "↓"}&nbsp;
</span>
</Show>
<span class="price">
<span class="currency">
{formattedPrice(props.price.amount, props.price.currency)}
</span>
<small> or </small>
<span class="kernings">
{formattedKernings(props.price.kernings)}&nbsp;KS+
</span>
</span>
</item-pricing>
<RarityIcon rarity={props.rarity} />
</div>
</item-description>
</A>
</layout-grid-item>
);
}
/*trimmed imports cause message is too long*/

export default function Item(props: GridLayout.ItemProps) {
function formattedPrice(amount: number, currency: string) {
return new Intl.NumberFormat("fr-CH", {
style: "currency",
currency,
currencyDisplay: "narrowSymbol",
}).format(amount);
}
const kerningsFormatter = new Intl.NumberFormat("fr-CH", {
style: "decimal",
useGrouping: true,
});
function formattedKernings(kernings: number) {
return kerningsFormatter.format(kernings);
}

return (
<layout-grid-item
role="article"
attr:locked={false}
attr:type={props.type}
attr:rarity={props.rarity}
>
<A href="/hello">
<item-image>
<StatusIcon remaining={props.remaining} />

<img src={props.image} alt="" />
</item-image>

<item-description>
<item-name>
<p class="author">{props.author}</p>
<p class="title">{props.title}</p>
<p class="publisher">{props.publisher}</p>
</item-name>

<hr />
<div>
<item-pricing>
<Show when={props.price.trend && props.price.trend !== "neutral"}>
<span class={`trend ${props.price.trend}`}>
{props.price.trend === "up" ? "↑" : "↓"}&nbsp;
</span>
</Show>
<span class="price">
<span class="currency">
{formattedPrice(props.price.amount, props.price.currency)}
</span>
<small> or </small>
<span class="kernings">
{formattedKernings(props.price.kernings)}&nbsp;KS+
</span>
</span>
</item-pricing>
<RarityIcon rarity={props.rarity} />
</div>
</item-description>
</A>
</layout-grid-item>
);
}
14 replies
SSolidJS
Created by apademide on 4/11/2023 in #support
frontend navigation: null is not an object (evaluating '_el$11.nextSibling')
14 replies
SSolidJS
Created by apademide on 4/11/2023 in #support
frontend navigation: null is not an object (evaluating '_el$11.nextSibling')
This is the component that causes the error.
14 replies