flippyflops
flippyflops
SSolidJS
Created by flippyflops on 8/23/2024 in #support
Is there a way to enforce children of a particular component type?
I'm wanting to make a component that only accepts children that are a specific component so for example:
// Valid
<CodeGroup>
<Code label="Ctrl" />
<Code label="M" />
</CodeGroup>

// Invalid
<CodeGroup>
<div>hi</div>
</CodeGroup>
// Valid
<CodeGroup>
<Code label="Ctrl" />
<Code label="M" />
</CodeGroup>

// Invalid
<CodeGroup>
<div>hi</div>
</CodeGroup>
I tried playing around with FlowProps and FlowComponent but couldn't quite get it to work. I think I'm pretty close but I'm not sure how I can allow passing the result of calling <Code /> while also enforcing the children are of type Code. So as a prop, items: (typeof Code)[] for example allows me pass in Code but not <Code />.
10 replies
SSolidJS
Created by flippyflops on 8/12/2024 in #support
Help with creating a builder pattern for main renderer
Wasn't sure how to fully articulate this but I want to have a class that lets me "build" the nested component structure that eventually gets passed into the render function. I think I have it working with runtime behavior, but the type definitions aren't quite right and was hoping I could get some feedback on it. Implementation
export class HOCBuilder {
private component: Component<Record<string, unknown>> = () => <></>;
private props?: ComponentProps<typeof this.component>;

with<T extends Component<ParentProps>>(
Wrapper: T,
wrapperProps?: ComponentProps<T>
): this {
const PreviousComponent = this.component;
const previousProps = this.props;
this.props = wrapperProps;

this.component = () => (
<Wrapper {...wrapperProps}>
<PreviousComponent {...previousProps} />
</Wrapper>
);
return this;
}

build(): Component {
return this.component;
}
}
export class HOCBuilder {
private component: Component<Record<string, unknown>> = () => <></>;
private props?: ComponentProps<typeof this.component>;

with<T extends Component<ParentProps>>(
Wrapper: T,
wrapperProps?: ComponentProps<T>
): this {
const PreviousComponent = this.component;
const previousProps = this.props;
this.props = wrapperProps;

this.component = () => (
<Wrapper {...wrapperProps}>
<PreviousComponent {...previousProps} />
</Wrapper>
);
return this;
}

build(): Component {
return this.component;
}
}
Usage
import { HOCBuilder, KeyboardShortcutManagerProvider } from "~/utilities";
import { type JSX } from "solid-js";
import { render } from "solid-js/web";

import App from "./App";
import { Router } from "./router";

function Foo(props: { bar: string }): JSX.Element {
return <>{props.bar}</>;
}

const Main = new HOCBuilder()
.with(Router, { root: App })
// This is giving an error! :'(
.with(Foo, { bar: "asdf" })
.with(KeyboardShortcutManagerProvider)
.build();

render(() => <Main />, document.getElementById("root")!);
import { HOCBuilder, KeyboardShortcutManagerProvider } from "~/utilities";
import { type JSX } from "solid-js";
import { render } from "solid-js/web";

import App from "./App";
import { Router } from "./router";

function Foo(props: { bar: string }): JSX.Element {
return <>{props.bar}</>;
}

const Main = new HOCBuilder()
.with(Router, { root: App })
// This is giving an error! :'(
.with(Foo, { bar: "asdf" })
.with(KeyboardShortcutManagerProvider)
.build();

render(() => <Main />, document.getElementById("root")!);
Argument of type '(props: { bar: string; }) => Element' is not assignable to parameter of type 'Component<{ children?: Element; }>'.
Types of parameters 'props' and 'props' are incompatible.
Property 'bar' is missing in type '{ children?: Element; }' but required in type '{ bar: string; }'.
Argument of type '(props: { bar: string; }) => Element' is not assignable to parameter of type 'Component<{ children?: Element; }>'.
Types of parameters 'props' and 'props' are incompatible.
Property 'bar' is missing in type '{ children?: Element; }' but required in type '{ bar: string; }'.
4 replies
SSolidJS
Created by flippyflops on 8/5/2024 in #support
Authenticated routes in SPA
I wanted to expose this question to more people and thought of this discord server. Original post in GH: https://github.com/solidjs/solid-router/discussions/364 The TL;DR; is that there doesn't seem to be a canonical approach to handling private vs public routes such that repetition is avoided -- I.E some type of middleware or guard. I posted what I'm currently playing around with and will add it here for feedback, though in its current state it isn't working as expected. When using nested routes in the config-based approach this doesn't work.
<Router root={App}>
<For each={routes}>
{(route) => {
// This approach assumes private by default
if (route.info?.public) {
return <Route component={route.component} />;
}

return (
<Route
component={(props) => (
// Where `AuthGuard` contains redirect/navigate logic
<AuthGuard>{route.component?.(props)}</AuthGuard>
)}
/>
);
}}
</For>
</Router>
<Router root={App}>
<For each={routes}>
{(route) => {
// This approach assumes private by default
if (route.info?.public) {
return <Route component={route.component} />;
}

return (
<Route
component={(props) => (
// Where `AuthGuard` contains redirect/navigate logic
<AuthGuard>{route.component?.(props)}</AuthGuard>
)}
/>
);
}}
</For>
</Router>
7 replies
SSolidJS
Created by flippyflops on 8/4/2024 in #support
useContext from function dynamically imported
I'm building a keyboard shortcut management system and wanted to be able to dynamically import the action associated with a keybinding. Some of these keybindings may use a context so I've intentionally put the keyboard shortcut manager provider inside of all other providers. action: () => import("../actions/switchColorScheme") and in my index file:
<I18nProvider>
<ColorSchemeProvider>
<KeyboardShortcutManagerProvider>
<Router root={App}>{routes}</Router>
</KeyboardShortcutManagerProvider>
</ColorSchemeProvider>
</I18nProvider>
<I18nProvider>
<ColorSchemeProvider>
<KeyboardShortcutManagerProvider>
<Router root={App}>{routes}</Router>
</KeyboardShortcutManagerProvider>
</ColorSchemeProvider>
</I18nProvider>
I'm getting an error when trying to useContext() in the action file's default function. Is that expected or is there a correct way of doing this? I thought that intentionally nesting the contexts where they'd normally work would be fine.
54 replies
SSolidJS
Created by flippyflops on 8/4/2024 in #support
Testing createResource with dynamic import
I'm trying to test an Icon component that I built that uses a dynamic import inside of a createResource. I attempted to mock createResource but couldn't get it to work as expected... I'll just post a simplified version of the component in the hopes that someone knows based off of the code. And FWIW I'm using vite-plugin-solid-svg to import svg files as solid components. Also, I was prompted to try to do this because I can run tests to get 100% coverage on statements, branches, and lines... but my function coverage was like 3%. It seems to be related to the dynamic import so perhaps I'm barking up the wrong tree with createResource?
import { createResource, Show } from "solid-js";

export function Icon(props) {
const [lazyIcon] = createResource(
() => props.icon,
async (iconName) => {
try {
const lazyFile = await import(`./icons/icon-${iconName}.svg?component-solid`);
return lazyFile.default;
} catch {
return null;
}
}
);

return (
<Show when={lazyIcon()} fallback={<div />}>
{(LoadedIcon) => (
<div id={props.id}}>
{LoadedIcon()}
</div>
)}
</Show>
);
}
import { createResource, Show } from "solid-js";

export function Icon(props) {
const [lazyIcon] = createResource(
() => props.icon,
async (iconName) => {
try {
const lazyFile = await import(`./icons/icon-${iconName}.svg?component-solid`);
return lazyFile.default;
} catch {
return null;
}
}
);

return (
<Show when={lazyIcon()} fallback={<div />}>
{(LoadedIcon) => (
<div id={props.id}}>
{LoadedIcon()}
</div>
)}
</Show>
);
}
2 replies
SSolidJS
Created by flippyflops on 6/18/2024 in #support
Reactivity in store
I think I may be missing the point of stores or just how to use them because I simply can't get it to trigger reactivity in a component. Below is a barebones example where I'm trying to have a value in a store and a function to update it - when the value changes I want to render it on the screen in a paragraph tag. Is there some obvious thing I'm missing here?
import { createContext, ParentProps, useContext } from "solid-js";
import { createStore } from "solid-js/store";

const FoobarContext = createContext<{
name: string;
changeName: () => void;
}>();

export const FoobarProvider = (props: ParentProps<unknown>) => {
const [store, setStore] = createStore({
name: "Jeff",
});

return (
<FoobarContext.Provider
value={{
...store,
changeName: () => {
setStore("name", "John");
},
}}
>
{props.children}
</FoobarContext.Provider>
);
};

const FoobarComponent = () => {
const { name, changeName } = useContext(FoobarContext)!;

return (
<>
<p>{name}</p>
<button onClick={changeName}>Change Name</button>
</>
);
};

export const Foobar = () => (
<FoobarProvider>
<FoobarComponent />
</FoobarProvider>
);
import { createContext, ParentProps, useContext } from "solid-js";
import { createStore } from "solid-js/store";

const FoobarContext = createContext<{
name: string;
changeName: () => void;
}>();

export const FoobarProvider = (props: ParentProps<unknown>) => {
const [store, setStore] = createStore({
name: "Jeff",
});

return (
<FoobarContext.Provider
value={{
...store,
changeName: () => {
setStore("name", "John");
},
}}
>
{props.children}
</FoobarContext.Provider>
);
};

const FoobarComponent = () => {
const { name, changeName } = useContext(FoobarContext)!;

return (
<>
<p>{name}</p>
<button onClick={changeName}>Change Name</button>
</>
);
};

export const Foobar = () => (
<FoobarProvider>
<FoobarComponent />
</FoobarProvider>
);
18 replies