S
SolidJSβ€’5mo ago
zobweyt

Context with children(() => props.children) helper triggers an error

Hey! I've been trying to create a Stepper component in which I'd have StepperContext with its API. however, somehow I need to control the displayed children, I found the children helper but it doesn't work with context https://playground.solidjs.com/anonymous/92dca985-17bc-4cbb-b2d8-d4b95fb33a33 am I missing something?
Solid Playground
Quickly discover what the solid compiler will generate from your JSX template
73 Replies
Brendonovich
Brendonovichβ€’5mo ago
children isn't necessary in that example, it's for more advanced use cases where you would otherwise be accessing props.children more than once the reason it errors is because children is basically createMemo - it eagerly evaluates the function inside it, which happens outside of the context
zobweyt
zobweytOPβ€’5mo ago
but how to control the displayed child? I would like children to use context to control it for example, if the currentIndex is 0, then the first child will be shown yeah the children helper above the context provider executes all children outside of the context
bigmistqke
bigmistqkeβ€’5mo ago
you don't need the children, but for stepper.currentIndex() to be reactive you have to wrap it in jsx
bigmistqke
bigmistqkeβ€’5mo ago
Solid Playground
Quickly discover what the solid compiler will generate from your JSX template
Brendonovich
Brendonovichβ€’5mo ago
in that case you would need children something like this
export function Stepper(props: ParentProps) {
const [currentIndex, setCurrentIndex] = createSignal(0);

return (
<StepperContext.Provider value={{ currentIndex }}>
<StepperInner>{props.children}</StepperInner>
</StepperContext.Provider>
);
}

function StepperInner(props: ParentProps) {
const { currentIndex } = useStepper();

const c = children(() => props.children);

return <>{c.toArray()[currentIndex()]}</>
}
export function Stepper(props: ParentProps) {
const [currentIndex, setCurrentIndex] = createSignal(0);

return (
<StepperContext.Provider value={{ currentIndex }}>
<StepperInner>{props.children}</StepperInner>
</StepperContext.Provider>
);
}

function StepperInner(props: ParentProps) {
const { currentIndex } = useStepper();

const c = children(() => props.children);

return <>{c.toArray()[currentIndex()]}</>
}
zobweyt
zobweytOPβ€’5mo ago
should the currentIndex be updated each second there? seems like nothing happens
bigmistqke
bigmistqkeβ€’5mo ago
no it's a timeout, not an interval
zobweyt
zobweytOPβ€’5mo ago
oh
bigmistqke
bigmistqkeβ€’5mo ago
Solid Playground
Quickly discover what the solid compiler will generate from your JSX template
zobweyt
zobweytOPβ€’5mo ago
yeah got it! thank you :)
bigmistqke
bigmistqkeβ€’5mo ago
you are welcome you can think of jsx as if it is an effect so just calling a signal inside the body of a component will not be reactive, but if you call it inside an effect/memo/jsx it will be but if the goal is to display a certain html-element from its children, you maybe don't need context:
function Stepper(props: ParentProps) {
const [currentIndex, setCurrentIndex] = createSignal(0);

const c = children(() => props.children);

return <>{c.toArray()[currentIndex()]}</>
}
function Stepper(props: ParentProps) {
const [currentIndex, setCurrentIndex] = createSignal(0);

const c = children(() => props.children);

return <>{c.toArray()[currentIndex()]}</>
}
like brendonovich showed
zobweyt
zobweytOPβ€’5mo ago
the goal is to give the control of current index to Steps. so you can skip a step inside a step for example inside a step...
return <>
<button type="button" onClick={() => stepper.setCurrentIndex(stepper.currentIndex() + 2)} />
</>
return <>
<button type="button" onClick={() => stepper.setCurrentIndex(stepper.currentIndex() + 2)} />
</>
bigmistqke
bigmistqkeβ€’5mo ago
instepcion so the context should pass the setter and not the getter?
export function Stepper(props: ParentProps) {
const [currentIndex, setCurrentIndex] = createSignal(0);
const c = children(() => props.children);
return (
<StepperContext.Provider value={{ setCurrentIndex }}>
{c.toArray()[currentIndex()]}
</StepperContext.Provider>
);
}
export function Stepper(props: ParentProps) {
const [currentIndex, setCurrentIndex] = createSignal(0);
const c = children(() => props.children);
return (
<StepperContext.Provider value={{ setCurrentIndex }}>
{c.toArray()[currentIndex()]}
</StepperContext.Provider>
);
}
?
zobweyt
zobweytOPβ€’5mo ago
oh, typo
bigmistqke
bigmistqkeβ€’5mo ago
o wait lol now i make the same mistake again haha
zobweyt
zobweytOPβ€’5mo ago
because in this way you won't be able to access the current index in a step
bigmistqke
bigmistqkeβ€’5mo ago
it should wrap the children in the context ofcourse:
export function Stepper(props: ParentProps) {
const [currentIndex, setCurrentIndex] = createSignal(0);
const c = children(() => (
<StepperContext.Provider value={{ setCurrentIndex, currentIndex }}>
{props.children}
</StepperContext.Provider>
));
return <>{c.toArray()[currentIndex()]}</>
}
export function Stepper(props: ParentProps) {
const [currentIndex, setCurrentIndex] = createSignal(0);
const c = children(() => (
<StepperContext.Provider value={{ setCurrentIndex, currentIndex }}>
{props.children}
</StepperContext.Provider>
));
return <>{c.toArray()[currentIndex()]}</>
}
you can pass it in the context too, I wonder if you ever really need it stepper.setCurrentIndex(currentIndex => currentIndex + 2)
zobweyt
zobweytOPβ€’5mo ago
oh that's cool
bigmistqke
bigmistqkeβ€’5mo ago
but you will know that better then me, I am still a bit unclear what a stepper-component does πŸ™‚
zobweyt
zobweytOPβ€’5mo ago
so you don't need a wrapper here maybe Stepper is not the best name for this component, but I'd like to achieve such functionality where you don't really have "previous" and "next" buttons, so the steps itself control the index maybe a function like setNext and setPrevious would be fine instead
bigmistqke
bigmistqkeβ€’5mo ago
might be a bit less performant πŸ€”
zobweyt
zobweytOPβ€’5mo ago
why?
bigmistqke
bigmistqkeβ€’5mo ago
because children() will be called everytime props.children gets called but not sure tbh what is the practical usecase for it: like a gallery of images?
zobweyt
zobweytOPβ€’5mo ago
I guess this option is even more flexible because it'll be easier for you to separate steps from other elements (like stepper header or footer) so you don't need to filter the elements to get the steps a form. for example, at first step you enter an email, then if a user with such email exists, it shows password, otherwise it requests verification code sent to the email so if a user exists is a check at the first step, if this condition is true, then the verification step is skipped or if a user enabled 2fa in their settings, there could also be a step for this and you can skip it if user disabled it
bigmistqke
bigmistqkeβ€’5mo ago
gotcha i see the big picture now
zobweyt
zobweytOPβ€’5mo ago
so using a stepper for this is kinda convenient
bigmistqke
bigmistqkeβ€’5mo ago
makes sens I think one thing to keep in mind is that children(() => ...) returns dom-elements
zobweyt
zobweytOPβ€’5mo ago
yeah so you can't just filter it like select children which are typeof Step so I said this
bigmistqke
bigmistqkeβ€’5mo ago
so
const StepperChild = () => <>
<div>hallo</div>
<span>ok</span>
</>
const StepperChild = () => <>
<div>hallo</div>
<span>ok</span>
</>
will create 2 steps
zobweyt
zobweytOPβ€’5mo ago
oh really just wrap in a div?
bigmistqke
bigmistqkeβ€’5mo ago
yes, because it returns 2 dom elements yes, that's a solution, just something to be aware about
zobweyt
zobweytOPβ€’5mo ago
I'd make a generic Step component with aria attributes and a wrapper to avoid this
bigmistqke
bigmistqkeβ€’5mo ago
<Stepper
steps={[
<Step1/>,
<Step2/>
]}
/>
<Stepper
steps={[
<Step1/>,
<Step2/>
]}
/>
with this type of setup you can make it work but a bit of an uglier api
zobweyt
zobweytOPβ€’5mo ago
yeah so a wrapper like Stepper.Steps would be fine I guess
bigmistqke
bigmistqkeβ€’5mo ago
singular Stepper.Step u mean?
zobweyt
zobweytOPβ€’5mo ago
no for example..
<Stepper>
<MyHeader/>
<Stepper.Steps>
<Step />
<Step />
<Step />
</Stepper.Steps>
<MyFooter/>
</Stepper>
<Stepper>
<MyHeader/>
<Stepper.Steps>
<Step />
<Step />
<Step />
</Stepper.Steps>
<MyFooter/>
</Stepper>
so you can easily divide elements
bigmistqke
bigmistqkeβ€’5mo ago
in this case <MyHeader/> and <MyFooter/> would be static?
zobweyt
zobweytOPβ€’5mo ago
yes like header would show the current index and footer would have buttons to set next or previous steps its flexible
bigmistqke
bigmistqkeβ€’5mo ago
that is possible it's a possible feature but it's a bit a different story
zobweyt
zobweytOPβ€’5mo ago
because <MyHeader/> is in the StepperContext
bigmistqke
bigmistqkeβ€’5mo ago
ye, I would probably keep it that everything inside <Stepper/> changes but that's me if you have a wrapper-component that returns a div you can mark this div too and actually filter on it inside the Stepper-component
bigmistqke
bigmistqkeβ€’5mo ago
Solid Playground
Quickly discover what the solid compiler will generate from your JSX template
zobweyt
zobweytOPβ€’5mo ago
you'd get something like
<Stepper>
<Step />
<MyHeader/>
<Step />
<Step />
</Stepper>
<Stepper>
<Step />
<MyHeader/>
<Step />
<Step />
</Stepper>
bigmistqke
bigmistqkeβ€’5mo ago
or u could also add a symbol to the div and check for that inside Stepper I think there is a typo in that snippet
zobweyt
zobweytOPβ€’5mo ago
the type that <Step/> is before <MyHeader/>? it's possible to do if you don't have a wrapper so it's not the best API too
bigmistqke
bigmistqkeβ€’5mo ago
Solid Playground
Quickly discover what the solid compiler will generate from your JSX template
bigmistqke
bigmistqkeβ€’5mo ago
I am talking about </Stepper.Steps>
zobweyt
zobweytOPβ€’5mo ago
oh yes that's interesting but a bit more complicated
bigmistqke
bigmistqkeβ€’5mo ago
it allows you to filter and ignore children of Stepper that are not wrapped in a Step, you could also give a warning and stuff like that: https://playground.solidjs.com/anonymous/a749fcfb-6b0d-4fd5-b651-4bb98cae516b
Solid Playground
Quickly discover what the solid compiler will generate from your JSX template
zobweyt
zobweytOPβ€’5mo ago
I guess using a wrapper like Stepper.Steps is more straightforward
bigmistqke
bigmistqkeβ€’5mo ago
it doesn't solve the same problem tho a child of Stepper.Steps could still return multiple html-elements or could not be a Step
zobweyt
zobweytOPβ€’5mo ago
yeah I see the problem
bigmistqke
bigmistqkeβ€’5mo ago
it's a subtle difference with react where in react a child of jsx is the jsx and not the html-element
zobweyt
zobweytOPβ€’5mo ago
because they have virtual dom
bigmistqke
bigmistqkeβ€’5mo ago
exactly but ye I think you will figure it out! lmk when you completed it, could be handy!
zobweyt
zobweytOPβ€’5mo ago
okay :) thank you for the help! :)
zobweyt
zobweytOPβ€’5mo ago
Solid Playground
Quickly discover what the solid compiler will generate from your JSX template
zobweyt
zobweytOPβ€’5mo ago
it's not perfect yet, but the component API is done I'm wondering if there's a way to load steps lazily. for example, if the current step in a stepper UI is step 0, can I avoid loading the other steps (1 and 2) until they're actually needed? my current implementation loads all steps upfront (try putting onMount in a StepperStep with a console.log)
zobweyt
zobweytOPβ€’5mo ago
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β€’5mo ago
it's possible, but then you will need to do some more hackery
bigmistqke
bigmistqkeβ€’5mo ago
if you look at the implementation of <Switch><Match/></Switch> you will see that it returns an object instead of a dom-element or function (in this case the props) https://github.com/solidjs/solid/blob/61dd1e88dd41175cb1e753121c8974b50e207dbf/packages/solid/src/render/flow.ts#L232
GitHub
solid/packages/solid/src/render/flow.ts at 61dd1e88dd41175cb1e75312...
A declarative, efficient, and flexible JavaScript library for building user interfaces. - solidjs/solid
zobweyt
zobweytOPβ€’5mo ago
if I'd make it with <Switch><Match/></Switch>, then props.children in StepperSteps would always contain only the displayed element and I wouldn't be able to get the steps length i.e. stepper.setStepsCount(steps().length);
bigmistqke
bigmistqkeβ€’5mo ago
Solid Playground
Quickly discover what the solid compiler will generate from your JSX template
bigmistqke
bigmistqkeβ€’5mo ago
no i didn't mean to use Switch/Map, I meant how Switch/Map is able to lazily execute a JSX while it is already inside the markup
bigmistqke
bigmistqkeβ€’5mo ago
you will find https://github.com/solidjs/solid/discussions/2233 this conversation interesting btw
GitHub
[Help!!] Can I have both props from parent and props from children ...
Hello! I am trying to make my library to support SolidJs. But I am facing an obstacle and cannot find it. I checked all the relative articles and Q&A, but all of them are ended up suggesting a ...
bigmistqke
bigmistqkeβ€’5mo ago
most of the techniques I described here are also mentioned there
export function Match<T>(props: MatchProps<T>) {
return props as unknown as JSX.Element;
}
export function Match<T>(props: MatchProps<T>) {
return props as unknown as JSX.Element;
}
it's a very powerful pattern! solid's jsx is very flexible, if you don't mind a type-assertion from time to time
zobweyt
zobweytOPβ€’5mo ago
I'm confused now. children is null here for some reason https://playground.solidjs.com/anonymous/c2cfed22-39ae-433b-9b47-d0257c4a81f5
Solid Playground
Quickly discover what the solid compiler will generate from your JSX template
zobweyt
zobweytOPβ€’5mo ago
yeah it's convenient it's interesting now you don't need to call .toArray() seems like that's an issue with @solid-primitives/refs now it's not possible to filter it by symbol I guess I need to wrap ref creation it in a func f and then assign symbol to the f or even better wrap it with createMemo I guess that’s because objects returned now instead of an array of JSX elements
bigmistqke
bigmistqkeβ€’5mo ago
I m afk! Going on holiday 😁 but good luck with the component!
zobweyt
zobweytOPβ€’5mo ago
thanks! have a nice holiday! πŸ™ƒ
Madaxen86
Madaxen86β€’5mo ago
Here's been a similar discussion where I suggested to you a map or array on the parent node which provides the context where the children can add/remove themselves onMount/onCleanup and sort them by the Documentposition. And you could then also provides the functions to skip a step etc. https://discord.com/channels/722131463138705510/1258411547701542923/1258752653903794247
zobweyt
zobweytOPβ€’5mo ago
https://playground.solidjs.com/anonymous/edcd51d6-ee3f-4afb-a0d9-9847b4c1ef15 I think it's perfect now. it loads the steps lazily, caches them and saves the reactivity as well
Solid Playground
Quickly discover what the solid compiler will generate from your JSX template
zobweyt
zobweytOPβ€’5mo ago
interesting workaround
Want results from more Discord servers?
Add your server