S
SolidJS3w ago
Paul

Confused about context.

No poc, as there's too much code. Here's the dir of the component: https://github.com/paulm17/mantinesolid/tree/main/packages/%40mantine/core/src/component/Checkbox Here's the component in action:
https://mantine.dev/core/checkbox/#checkboxcard-component It's the one below the text:
You can use Checkbox.Card with Checkbox.Group the same way as Checkbox component:
Here is the context provider: https://github.com/paulm17/mantinesolid/blob/main/packages/%40mantine/core/src/component/Checkbox/CheckboxGroup/CheckboxGroup.tsx#L81 Here is the card onclick event: https://github.com/paulm17/mantinesolid/blob/main/packages/%40mantine/core/src/component/Checkbox/CheckboxCard/CheckboxCard.tsx#L130 All I can get is the onclick event and then nothing. What is annoying 😅 Checkbox Indicator on it's own - Works CheckboxCard and/or CheckboxIndicator - Works CheckboxGroup + CheckboxCard + Checkbox Indicator - Does not work uncontrolled/controlled
60 Replies
REEEEE
REEEEE3w ago
This part doesn't make sense to me, why is it a function?
No description
Paul
PaulOP3w ago
I have no idea. A lot of this boils down to. I simply don't understand solid. I have tried ctx.onChange(local.value || '') but then saw in corvu that pattern.
REEEEE
REEEEE3w ago
I mean that code wouldn't ever run because it's a function that's not called
Paul
PaulOP3w ago
It is being called. In the console I see: checkboxcard onClick @mantine/core checkboxcard onClick @mantine/hooks checkboxcard onClick @mantine/notifications sorry I am misunderstanding
REEEEE
REEEEE3w ago
The onclick is but the onChange wouldn't remove () => part
Paul
PaulOP3w ago
I have done and tried again. Nothing different happens.
Paul
PaulOP3w ago
This is what happens when I launch and then click.
No description
REEEEE
REEEEE3w ago
what happens if you log ctx
Paul
PaulOP3w ago
in which component?
REEEEE
REEEEE3w ago
in the onClick of checkboxcard
Paul
PaulOP3w ago
checkboxcard ctx {value: ƒ, onChange: ƒ, size: ƒ}
checkboxcard ctx {value: ƒ, onChange: ƒ, size: ƒ}
REEEEE
REEEEE3w ago
so it is getting the context just fine, so the onChange call should be firing Could it be how you're testing it? Is it a normal local dev or through storybook
Paul
PaulOP3w ago
This is normal dev. I can't get a storybook to work sadly.
REEEEE
REEEEE3w ago
Oh lol I see in the checkboxgroup, change onChange to onChange: handleChange
REEEEE
REEEEE3w ago
No description
REEEEE
REEEEE3w ago
It's just swallowing the stuff you pass to it and not calling the actual function
Paul
PaulOP3w ago
Nice, so now handleChange is firing. But then dies at setValue 😂 . Let me see if I can take it over here. Thanks for the help again, appreciate it!
REEEEE
REEEEE3w ago
Nice! Just for sanity checking, make sure stuff like this is a function, it could cause issues
No description
Paul
PaulOP3w ago
do you mean like here for the Box component underneath?
mod={[{ checked: () => _checked, disabled: local.disabled }, local.mod]}
mod={[{ checked: () => _checked, disabled: local.disabled }, local.mod]}
onChange: () => local.onChange,
onChange: () => local.onChange,
Was doing it again. Shouldn't have looked at that other repo lol 😂
REEEEE
REEEEE3w ago
I meant _checked should be a function like this:
const _checked = () =>
typeof local.checked === 'boolean' || typeof local.indeterminate === 'boolean'
? checked() || indeterminate()
: cardStoreValue();
const _checked = () =>
typeof local.checked === 'boolean' || typeof local.indeterminate === 'boolean'
? checked() || indeterminate()
: cardStoreValue();
Just so if the props checked or indeterminate changes, it'll be reactive for safety
Paul
PaulOP3w ago
So I am able to get the context + uncontrolled inputs to work. CheckboxIndicator.tsx:
const checked = () => local.checked;
const indeterminate = () => local.indeterminate;
const cardStoreValue = () => ctx?.checked();
const _checked =
typeof local.checked === 'boolean' || typeof local.indeterminate === 'boolean'
? checked || indeterminate
: () => cardStoreValue;
const checked = () => local.checked;
const indeterminate = () => local.indeterminate;
const cardStoreValue = () => ctx?.checked();
const _checked =
typeof local.checked === 'boolean' || typeof local.indeterminate === 'boolean'
? checked || indeterminate
: () => cardStoreValue;
CheckboxCard.tsx
const ctx = useCheckboxGroupContext();
const checked = () => local.checked;
const groupStoreValue = () => ctx ? ctx.value().includes(local.value || '') : undefined;
const _checked = () => typeof local.checked === 'boolean'? checked() : groupStoreValue();

const [_value, setValue] = useControlled({
value: _checked,
initialValue: local.defaultChecked ?? false,
// finalValue: [],
onChange: local.onChange,
});
const ctx = useCheckboxGroupContext();
const checked = () => local.checked;
const groupStoreValue = () => ctx ? ctx.value().includes(local.value || '') : undefined;
const _checked = () => typeof local.checked === 'boolean'? checked() : groupStoreValue();

const [_value, setValue] = useControlled({
value: _checked,
initialValue: local.defaultChecked ?? false,
// finalValue: [],
onChange: local.onChange,
});
When an option changes, see all 3 flash. Shouldn't only 1 be flashing?
Paul
PaulOP3w ago
MantineProvider mounted
MantineThemeProvider.tsx:48 MantineThemeProvider mounted
3 CheckboxGroup.tsx:79 checkboxGroup mounted
MantineProvider mounted
MantineThemeProvider.tsx:48 MantineThemeProvider mounted
3 CheckboxGroup.tsx:79 checkboxGroup mounted
Found out that the CheckboxGroup is somehow mounting 3 times. 😩 So something upstream is incorrect.
REEEEE
REEEEE3w ago
Probably the factory function Change the part that calls the ui function to be untrack(() => ui(props, refHandler)
Paul
PaulOP3w ago
Thanks. Didn't have any effect also I tried to memoize it and no effect either.
REEEEE
REEEEE3w ago
It's definitely upstream but only the factory function stuff looks like it would trigger the issue 🤔 Is the withProps subcomppnent of the factory function being used? It could also be causing it if you dont untrack
Paul
PaulOP3w ago
It's not being used. It seems to be Box but I can't fix the issue: The AST
App
@mantine/core/MantineProvider
@mantine/core/MantineThemeProvider
StoryComponent
@mantine/core/Stack
@mantine/core/Box
@mantine/core/Text
@mantine/core/Box
Show
Dynamic
Show
Dynamic
@mantine/core/Text
@mantine/core/Box
Show
Dynamic
App
@mantine/core/MantineProvider
@mantine/core/MantineThemeProvider
StoryComponent
@mantine/core/Stack
@mantine/core/Box
@mantine/core/Text
@mantine/core/Box
Show
Dynamic
Show
Dynamic
@mantine/core/Text
@mantine/core/Box
Show
Dynamic
I have trimmed down the component down to:
<div style={{ 'padding': '40px' }}>
<Stack>
<Text fz="xs" mt="md">
CurrentValue: {value2().join(', ') || '–'}
</Text>
</Stack>
</div>
<div style={{ 'padding': '40px' }}>
<Stack>
<Text fz="xs" mt="md">
CurrentValue: {value2().join(', ') || '–'}
</Text>
</Stack>
</div>
So only 1 Text should be displayed.
const parsedStyleProps = createMemo(() =>
parseStyleProps({
styleProps,
theme,
data: STYlE_PROPS_DATA,
})
);

const styleMemo = createMemo(() => ({
selector: `.${responsiveClassName}`,
styles: parsedStyleProps().styles,
media: parsedStyleProps().media,
has: parsedStyleProps().hasResponsiveStyles,
}));

const Host = createMemo(() => local.component || 'div')();

const elementProps = createMemo(() => ({
ref: local.ref,
style: getBoxStyle({
theme,
style: local.style,
vars: local.__vars,
styleProps: parsedStyleProps().inlineStyles,
}),
className: cx(
local.className,
transformedSx(),
{
[responsiveClassName]: parsedStyleProps().hasResponsiveStyles,
'mantine-light-hidden': local.lightHidden,
'mantine-dark-hidden': local.darkHidden,
[`mantine-hidden-from-${local.hiddenFrom}`]: local.hiddenFrom,
[`mantine-visible-from-${local.visibleFrom}`]: local.visibleFrom,
}
),
'data-variant': local.variant,
'data-size': isNumberLike(local.size) ? undefined : local.size || undefined,
size: local.__size,
...getBoxMod(local.mod),
...rest,
}));

const hostFn = typeof local.renderRoot === 'function'
? local.renderRoot
: undefined;

return (
<>
<Show when={styleMemo().has}>
<InlineStyles
selector={styleMemo().selector}
styles={styleMemo().styles}
media={styleMemo().media}
/>
</Show>

{hostFn ? (
hostFn(elementProps())
) : (
<Dynamic component={Host} {...elementProps()}>
{props.children}
</Dynamic>
)}
</>
);
};
const parsedStyleProps = createMemo(() =>
parseStyleProps({
styleProps,
theme,
data: STYlE_PROPS_DATA,
})
);

const styleMemo = createMemo(() => ({
selector: `.${responsiveClassName}`,
styles: parsedStyleProps().styles,
media: parsedStyleProps().media,
has: parsedStyleProps().hasResponsiveStyles,
}));

const Host = createMemo(() => local.component || 'div')();

const elementProps = createMemo(() => ({
ref: local.ref,
style: getBoxStyle({
theme,
style: local.style,
vars: local.__vars,
styleProps: parsedStyleProps().inlineStyles,
}),
className: cx(
local.className,
transformedSx(),
{
[responsiveClassName]: parsedStyleProps().hasResponsiveStyles,
'mantine-light-hidden': local.lightHidden,
'mantine-dark-hidden': local.darkHidden,
[`mantine-hidden-from-${local.hiddenFrom}`]: local.hiddenFrom,
[`mantine-visible-from-${local.visibleFrom}`]: local.visibleFrom,
}
),
'data-variant': local.variant,
'data-size': isNumberLike(local.size) ? undefined : local.size || undefined,
size: local.__size,
...getBoxMod(local.mod),
...rest,
}));

const hostFn = typeof local.renderRoot === 'function'
? local.renderRoot
: undefined;

return (
<>
<Show when={styleMemo().has}>
<InlineStyles
selector={styleMemo().selector}
styles={styleMemo().styles}
media={styleMemo().media}
/>
</Show>

{hostFn ? (
hostFn(elementProps())
) : (
<Dynamic component={Host} {...elementProps()}>
{props.children}
</Dynamic>
)}
</>
);
};
Everything is memoized. 🤦‍♂️
REEEEE
REEEEE3w ago
Well you dont want everything to be memoized It's not like react memo where it can diff components It could be the multiple use of props.children in the Box component I think just doing {props.children} should be good enough
Paul
PaulOP3w ago
I removed all that and started again
{/* {typeof local.renderRoot === 'function' ? (
local.renderRoot(elementProps())
) : (
// <Dynamic component={Element} {...elementProps()}>
// {props.children instanceof HTMLCollection
// ? Array.from(props.children)
// : props.children}
// </Dynamic>
<Dynamic component={Element} {...elementProps()}>
{children(() => props.children)}
</Dynamic>
)} */}
{typeof local.renderRoot === 'function' ? (
local.renderRoot(elementProps())
) : (
(() => {
const props = elementProps();
if (typeof Element === 'string') {
// For HTML elements like 'div', 'span', etc.
return <div {...props}>{props.children}</div>;
} else if (typeof Element === 'function') {
// For custom components
return <Element {...props}>{props.children}</Element>;
} else {
// Fallback to div
return <div {...props}>{props.children}</div>;
}
})()
)}
{/* {typeof local.renderRoot === 'function' ? (
local.renderRoot(elementProps())
) : (
// <Dynamic component={Element} {...elementProps()}>
// {props.children instanceof HTMLCollection
// ? Array.from(props.children)
// : props.children}
// </Dynamic>
<Dynamic component={Element} {...elementProps()}>
{children(() => props.children)}
</Dynamic>
)} */}
{typeof local.renderRoot === 'function' ? (
local.renderRoot(elementProps())
) : (
(() => {
const props = elementProps();
if (typeof Element === 'string') {
// For HTML elements like 'div', 'span', etc.
return <div {...props}>{props.children}</div>;
} else if (typeof Element === 'function') {
// For custom components
return <Element {...props}>{props.children}</Element>;
} else {
// Fallback to div
return <div {...props}>{props.children}</div>;
}
})()
)}
With dynamic. I get 3 Text components. With that function and divs. I get 1 Text component. Adding children to splitProps and then
{typeof local.renderRoot === 'function' ? (
local.renderRoot(elementProps())
) : (
<Dynamic component={Element} {...elementProps()}>
{local.children}
</Dynamic>
)}
{typeof local.renderRoot === 'function' ? (
local.renderRoot(elementProps())
) : (
<Dynamic component={Element} {...elementProps()}>
{local.children}
</Dynamic>
)}
resolved the issue 🤦‍♂️
REEEEE
REEEEE3w ago
hah nice
Paul
PaulOP3w ago
children to splitProps was the key. Without it, I get 3x Text. I'm actually on the verge of throwing in the towel. Solid makes no sense compared to React.
REEEEE
REEEEE3w ago
haha well using React patterns in Solid will do that to you
Paul
PaulOP3w ago
I'm actually not trying to do that. I'm coming in with zero knowledge and being humble and wanting to honestly debug this. And I get zero feedback. 😄 Also the chrome devtools work when they want to. 😂
REEEEE
REEEEE3w ago
That's fair, there's some level of fundamentals to understand about the differences / how Solid operates that makes the migration a bit challenging I haven't used the devtools in forever
Paul
PaulOP3w ago
Also I successfully got the CheckboxGroup to work with context and uncontrolled inputs. Then I toggled the main component to use controlled. Guess what, now the CheckboxGroup no longer works 😂 And unable to debug again. The console.logs I had previously don't fire. 🤦‍♂️
REEEEE
REEEEE3w ago
welp haha
Paul
PaulOP3w ago
exactly 😂
REEEEE
REEEEE3w ago
I would just make sure anything that reads a reactive source (props or a signal) are functions
Paul
PaulOP3w ago
Now the console.logs are starting to look normal. Before I fixed box. I had 81 checked 😂
No description
Paul
PaulOP3w ago
@REEEEE thanks so much for the help!
Paul
PaulOP3w ago
Paul
PaulOP3w ago
Finally, all working. Controlled/Uncontrolled and With/without context. The only issue. Is that CheckboxGroup-Controlled with react. Allows you to have Option 1 and 2 both selected at the same time. I'm putting that down to a react thing. Not possible with solid.
REEEEE
REEEEE3w ago
Nice! You definitely should be able to have both selected, there shouldn't be anything solid or react specific stopping that from happening
REEEEE
REEEEE3w ago
My guess is that this logic is breaking
No description
REEEEE
REEEEE3w ago
in checkbox
REEEEE
REEEEE3w ago
It's empty
Paul
PaulOP3w ago
https://codesandbox.io/p/devbox/mantine-react-template-forked-wx965g?workspaceId=ws_66SZtu7eK3Fdhpmgo4DbQv Checkbox isn't used for these components. It's either UnstyledButton or Box.
REEEEE
REEEEE3w ago
Need an account :P
Paul
PaulOP3w ago
I give up then. 😅
REEEEE
REEEEE3w ago
Could you show me the code for that demo?
Paul
PaulOP3w ago
import { CheckboxCard, CheckboxGroup, CheckboxIndicator, Stack } from "@mantine/core";
import { createSignal } from "solid-js";

export default function () {
const [value, setValue] = createSignal<string[]>(['1']);

return (
<Stack>
<CheckboxGroup value={value()} onChange={setValue}>
<CheckboxCard
value="1"
checked={value().includes('1')}
onClick={() => setValue((current) => (current.includes('1') ? [] : ['1']))}
>
<CheckboxIndicator />
Option 1
</CheckboxCard>

<CheckboxCard
value="2"
checked={value().includes('2')}
onClick={() => setValue((current) => (current.includes('2') ? [] : ['2']))}
>
<CheckboxIndicator />
Option 2
</CheckboxCard>
</CheckboxGroup>

<div>{JSON.stringify(value())}</div>
</Stack>
);
}
import { CheckboxCard, CheckboxGroup, CheckboxIndicator, Stack } from "@mantine/core";
import { createSignal } from "solid-js";

export default function () {
const [value, setValue] = createSignal<string[]>(['1']);

return (
<Stack>
<CheckboxGroup value={value()} onChange={setValue}>
<CheckboxCard
value="1"
checked={value().includes('1')}
onClick={() => setValue((current) => (current.includes('1') ? [] : ['1']))}
>
<CheckboxIndicator />
Option 1
</CheckboxCard>

<CheckboxCard
value="2"
checked={value().includes('2')}
onClick={() => setValue((current) => (current.includes('2') ? [] : ['2']))}
>
<CheckboxIndicator />
Option 2
</CheckboxCard>
</CheckboxGroup>

<div>{JSON.stringify(value())}</div>
</Stack>
);
}
It's the same as this.
Paul
PaulOP3w ago
Here's a short video for react
REEEEE
REEEEE3w ago
huh that's really weird behaviour because the logic says that it's setting the state to either 1 or 2 when you click but for some reason both are being selected? Like if you wanted the same behaviour in solid, it would be more like this
import { CheckboxCard, CheckboxGroup, CheckboxIndicator, Stack } from "@mantine/core";
import { createSignal } from "solid-js";

export default function () {
const [value, setValue] = createSignal<string[]>(['1']);

return (
<Stack>
<CheckboxGroup value={value()} onChange={setValue}>
<CheckboxCard
value="1"
checked={value().includes('1')}
onClick={() => setValue((current) => (current.includes('1') ? current.filter(v => v === '1') : [...current, '1']))}
>
<CheckboxIndicator />
Option 1
</CheckboxCard>

<CheckboxCard
value="2"
checked={value().includes('2')}
onClick={() => setValue((current) => (current.includes('2') ? current.filter(v => v === '2') : [...current, '2']))}
>
<CheckboxIndicator />
Option 2
</CheckboxCard>
</CheckboxGroup>

<div>{JSON.stringify(value())}</div>
</Stack>
);
}
import { CheckboxCard, CheckboxGroup, CheckboxIndicator, Stack } from "@mantine/core";
import { createSignal } from "solid-js";

export default function () {
const [value, setValue] = createSignal<string[]>(['1']);

return (
<Stack>
<CheckboxGroup value={value()} onChange={setValue}>
<CheckboxCard
value="1"
checked={value().includes('1')}
onClick={() => setValue((current) => (current.includes('1') ? current.filter(v => v === '1') : [...current, '1']))}
>
<CheckboxIndicator />
Option 1
</CheckboxCard>

<CheckboxCard
value="2"
checked={value().includes('2')}
onClick={() => setValue((current) => (current.includes('2') ? current.filter(v => v === '2') : [...current, '2']))}
>
<CheckboxIndicator />
Option 2
</CheckboxCard>
</CheckboxGroup>

<div>{JSON.stringify(value())}</div>
</Stack>
);
}
Paul
PaulOP3w ago
Yeah, I came to the very same conclusion.
REEEEE
REEEEE3w ago
so idk if that's a react bug, mantine bug, or I'm not understanding something with the components
Paul
PaulOP3w ago
For right now, I'm chalking it down to a react bug. Nothing really jumps out when I review the code. But I might ask Vitaly when I have completed the poc. Now I have a really complex component under my belt. I think I can do the rest of them. 22 / 84 done so far
REEEEE
REEEEE3w ago
Nice, looking forward to seeing it complete
Paul
PaulOP3w ago
@REEEEE just to confirm. Should I be seeing reactive for all 3 when I toggle 1? Is this a case where I should be using Index?
REEEEE
REEEEE3w ago
technically yeah but I can't confirm because I'm not sure what the devtools record Just to clarify my thinking/explain why you see all 3 reacting, it's because it's a signal and every time you set the value you're setting a new array and all the signals that do the "includes(myValue)" thing are triggered.
Paul
PaulOP3w ago
Thanks! It's a parent component rather than the individual ones. So I can change it easily. I'll do so and then see what the difference is.

Did you find this page helpful?