S
SolidJS12mo ago
t

My first solid component

Just wrote my first component. I only wrote React before so I wanted to know if there are better ways to do things than what I figured out so far :)
import { createBreakpoints } from "@solid-primitives/media";
import { children, For, mergeProps } from "solid-js";

import type { JSX, ParentComponent } from "solid-js";

interface ColumnsProps extends JSX.HTMLAttributes<HTMLDivElement> {
columns?: number | Record<number, string>;
}

export const Columns: ParentComponent<ColumnsProps> = (props) => {
const merged = mergeProps({ columns: 3 }, props);

return (
<div {...merged}>
<For
each={getColumnArray({
children: children(() => merged.children).toArray(),
columnAmount:
typeof merged.columns === "object"
? Object.entries(createBreakpoints(merged.columns)).reduce(
(acc, [key, value]) => (value ? Number(key) : acc),
1,
)
: merged.columns,
})}
>
{(column) => <div>{column}</div>}
</For>
</div>
);
};

interface GetColumnArray {
children: JSX.Element[];
columnAmount: number;
}

const getColumnArray = ({ children, columnAmount }: GetColumnArray) =>
children.reduce(
(acc: JSX.Element[][], child, index) => (acc[index % columnAmount]?.push(child), acc),
Array.from({ length: columnAmount }, () => []),
);
import { createBreakpoints } from "@solid-primitives/media";
import { children, For, mergeProps } from "solid-js";

import type { JSX, ParentComponent } from "solid-js";

interface ColumnsProps extends JSX.HTMLAttributes<HTMLDivElement> {
columns?: number | Record<number, string>;
}

export const Columns: ParentComponent<ColumnsProps> = (props) => {
const merged = mergeProps({ columns: 3 }, props);

return (
<div {...merged}>
<For
each={getColumnArray({
children: children(() => merged.children).toArray(),
columnAmount:
typeof merged.columns === "object"
? Object.entries(createBreakpoints(merged.columns)).reduce(
(acc, [key, value]) => (value ? Number(key) : acc),
1,
)
: merged.columns,
})}
>
{(column) => <div>{column}</div>}
</For>
</div>
);
};

interface GetColumnArray {
children: JSX.Element[];
columnAmount: number;
}

const getColumnArray = ({ children, columnAmount }: GetColumnArray) =>
children.reduce(
(acc: JSX.Element[][], child, index) => (acc[index % columnAmount]?.push(child), acc),
Array.from({ length: columnAmount }, () => []),
);
No description
22 Replies
t
tOP12mo ago
Code and image are the same, I attached it because I find Discord's codeblocks hard to read
bigmistqke
bigmistqke12mo ago
1. What is the use of the .toArray() with the children? 2. children(...) returns a signal containing the resolved children. To get the actual children you would need to call it. 3. Something to look out for: props.children can either be a single element or an array of elements.
t
tOP12mo ago
1. that's the solution I found for iterating over children
No description
t
tOP12mo ago
No description
t
tOP12mo ago
merged.children is of type JSX.Element | undefined so I can't iterate over it props.children is of the same type so I think 2. is wrong
bigmistqke
bigmistqke12mo ago
interesting! that stackoverflow example will not be reactive but in your case it will work fine, since you are calling children() inside the JSX
bigmistqke
bigmistqke12mo ago
did not know about toArray!
No description
bigmistqke
bigmistqke12mo ago
but this would be reactive:
function Stuff(){
const cs = children(() => props.children).toArray
return <For each={cs()}>...</For>
}
function Stuff(){
const cs = children(() => props.children).toArray
return <For each={cs()}>...</For>
}
cool!
thetarnav
thetarnav12mo ago
calling children in jsx will be reactive but it will also wastefully recreate memos each time it reruns so better to go with what @bigmistqke wrote also it currently has an issue that if collumns prop changes, props.children will be executed again, recreating elements
t
tOP12mo ago
I think it's reactive already?
No description
t
tOP12mo ago
At least this works Unless I'm misunderstanding
t
tOP12mo ago
No description
t
tOP12mo ago
This seems to be working fine too even though I'm getting a warning @ line 22 This function should be passed to a tracked scope (like createEffect) or an event handler because it contains reactivity, or else changes will be ignored. I can't tell whether this is a false positive or something is actually wrong and I don't understand it @joshwilsonvu can you help?
bigmistqke
bigmistqke12mo ago
but it will also wastefully recreate memos each time it reruns
you are still creating a memo each time. better to bring children() outside of getColumns and into ColumnsProps body like
const Columns = (props) => {
const childs = childen(() => props.children).toArray;
const getColumns = () => childs.reduce(...)
const Columns = (props) => {
const childs = childen(() => props.children).toArray;
const getColumns = () => childs.reduce(...)
bigmistqke
bigmistqke12mo ago
This function should be passed to a tracked scope (like createEffect) or an event handler because it contains reactivity, or else changes will be ignored.
this might be solved by renaming getColumns to createColumns see https://github.com/solidjs-community/eslint-plugin-solid/issues/112#issuecomment-1844195070
GitHub
Enable customizing reactive rule to treat certain custom functions ...
Describe the need I am making a library for my team which handles data fetching with our custom protocol. The library will take in the arguments to the API call and return a signal, very similar to...
thetarnav
thetarnav12mo ago
generally if something starts with create or use or returns an accessor instead of the value, you probably want to use it at high as possible (eg in component body, parent component or global store), instead of inlining it in jsx, memos, or functions like getColumns. also even if stuff is updating correctly on the screen you might want to add random console logs to various places to see if something isn't updating more then it should in solid functions should rerun only when needed, no more no less eg elements are good to check if they are not recreated for some reason, because that might be expensive and cause weird behaviors later, even though it seems ok because "it's reactive" <div ref={console.log} /> <- will log every time an element is created
t
tOP12mo ago
Childs isn't getting called here so you can't reduce it That didn't work I don't think I understand how memos work How do I check if one is being created
bigmistqke
bigmistqke12mo ago
oops typo, u right. should be
const Columns = (props) => {
const childs = childen(() => props.children).toArray;
const getColumns = () => childs().reduce(...)
const Columns = (props) => {
const childs = childen(() => props.children).toArray;
const getColumns = () => childs().reduce(...)
it's mentioned in the docs https://www.solidjs.com/docs/latest/api#children
The return value is a memo evaluating to the resolved children, which updates whenever the children change.
with a memo we mean createMemo: https://www.solidjs.com/docs/latest/api#creatememo . you can think of it as a combination of a createEffect and createSignal: a signal that only updates whenever one of its dependencies change.
t
tOP12mo ago
Oh that makes sense As for this I think I'll just disable eslint for that line
bigmistqke
bigmistqke12mo ago
ye the linter is quite aggressive.. i never use it in my own code tbh can u post the code? wanna check it in the playground
t
tOP12mo ago
import { createBreakpoints } from "@solid-primitives/media";
import { children, For, mergeProps } from "solid-js";

import type { JSX, ParentComponent } from "solid-js";

interface ColumnsProps extends JSX.HTMLAttributes<HTMLDivElement> {
columns?: number | Record<number, string>;
}

export const Columns: ParentComponent<ColumnsProps> = (_props) => {
const props = mergeProps({ columns: 3 }, _props);

const columnAmount = () =>
typeof props.columns === "object"
? Object.entries(createBreakpoints(props.columns)).reduce((acc, [key, value]) => (value ? Number(key) : acc), 1)
: props.columns;

const resolved = children(() => props.children).toArray;

const createColumns = () =>
resolved().reduce(
// eslint-disable-next-line solid/reactivity
(acc: JSX.Element[][], child, index) => (acc[index % columnAmount()]?.push(child), acc),
Array.from({ length: columnAmount() }, () => []),
);

return (
<div ref={console.log} {...props}>
<For each={createColumns()}>{(column) => <div>{column}</div>}</For>
</div>
);
};
import { createBreakpoints } from "@solid-primitives/media";
import { children, For, mergeProps } from "solid-js";

import type { JSX, ParentComponent } from "solid-js";

interface ColumnsProps extends JSX.HTMLAttributes<HTMLDivElement> {
columns?: number | Record<number, string>;
}

export const Columns: ParentComponent<ColumnsProps> = (_props) => {
const props = mergeProps({ columns: 3 }, _props);

const columnAmount = () =>
typeof props.columns === "object"
? Object.entries(createBreakpoints(props.columns)).reduce((acc, [key, value]) => (value ? Number(key) : acc), 1)
: props.columns;

const resolved = children(() => props.children).toArray;

const createColumns = () =>
resolved().reduce(
// eslint-disable-next-line solid/reactivity
(acc: JSX.Element[][], child, index) => (acc[index % columnAmount()]?.push(child), acc),
Array.from({ length: columnAmount() }, () => []),
);

return (
<div ref={console.log} {...props}>
<For each={createColumns()}>{(column) => <div>{column}</div>}</For>
</div>
);
};
bigmistqke
bigmistqke12mo ago
thanks 🙏 i feel that the linter should be able to recognise these situations.
createEffect(() => {
resolved().reduce(
(acc: JSX.Element[][], child, index) => {
acc[index % columnAmount()]?.push(child);
return acc;
},
Array.from({ length: columnAmount() }, () => []),
);
});
createEffect(() => {
resolved().reduce(
(acc: JSX.Element[][], child, index) => {
acc[index % columnAmount()]?.push(child);
return acc;
},
Array.from({ length: columnAmount() }, () => []),
);
});
gives the same linter-error
Want results from more Discord servers?
Add your server