can i propagate event to child?

is there a mechanism to propagate click event from parent to child? or is there any alternative? for example by returning component and a signal Accessor. but todo so, i need factory function. i want to avoid the need for factory function.
15 Replies
bokuno_disk_cord
bokuno_disk_cordOP2y ago
i can just export a global variable. but doing so prevent me create multiple instance of the component. or maybe my component could accept a callback? is that better?
Unknown User
Unknown User2y ago
Message Not Public
Sign In & Join Server To View
Otonashi
Otonashi2y ago
normally you don't pass whole events to children, and just pass some state via props but if you need to the solution is the same as with data, pass a signal containing the event via a prop to the child
bokuno_disk_cord
bokuno_disk_cordOP2y ago
So I have a list component (ul) what accept JSX.ArrayComponent as children. i want to be able to put anything to it as list item, like anchor element a or anything with onClick event attached to it. the problem is that the item container (li) is bigger than its child component. i want to be able to click on the container (not exactly at the text) and run an action. the item inside the li could be anything, from simple text to svg icon. each item may have different action. my current approach is to accept a callback that look like this.
type ItemClickCallback = (idx: number, el: JSX.Element) => void
type ItemClickCallback = (idx: number, el: JSX.Element) => void
the action dispatch/routing would be handled by the parent component.
Alex Lohr
Alex Lohr2y ago
You can just use a signal to capture the event and use it inside an effect in the child.
// parent
const [click, setClick] = createSignal();
return <div onClick={setClick} />

// client
createEffect(on(props.click, (ev) => { /* use ev like in the event handler */ }, { defer: true }));
// parent
const [click, setClick] = createSignal();
return <div onClick={setClick} />

// client
createEffect(on(props.click, (ev) => { /* use ev like in the event handler */ }, { defer: true }));
It should be noted that the reactive system will get the event out of the event cycle, so things that require authentic events (eg starting video) will not work.
bokuno_disk_cord
bokuno_disk_cordOP2y ago
i have the same thought, to use effect. but idk how to do so. because i dont have control over the children. how can i pass the accessor or click, to the children?
Alex Lohr
Alex Lohr2y ago
yes. using props
bokuno_disk_cord
bokuno_disk_cordOP2y ago
oh, so i pass the accessor to the children and pass setter to the list?
Alex Lohr
Alex Lohr2y ago
yes. if you need to react on the actual event, you can also turn the pattern on its head: give a setter to the client that allows to set the event handler.
// parent
const [clickHandler, setClickHandler] = createSignal();
return <div onClick={(ev) => {
const handler = clickHandler();
if (handler) { handler(ev); }
}}><Child setClickHandler={setClickHandler} /></div>

// child - you need to use the manipulation pattern to set a function
props.setClickHandler(() => (ev) => ...);
// parent
const [clickHandler, setClickHandler] = createSignal();
return <div onClick={(ev) => {
const handler = clickHandler();
if (handler) { handler(ev); }
}}><Child setClickHandler={setClickHandler} /></div>

// child - you need to use the manipulation pattern to set a function
props.setClickHandler(() => (ev) => ...);
bokuno_disk_cord
bokuno_disk_cordOP2y ago
idk how to do it with this method.
Alex Lohr
Alex Lohr2y ago
What do you mean, you don't have control over the children? What exactly are you trying to achieve?
bokuno_disk_cord
bokuno_disk_cordOP2y ago
interface event {
idx: number;
el: JSXElement;
}

const List: FlowComponent<{ click: Setter<event> }, JSXElement[]> = (props) => {
return (
<ul>
<For each={props.children}>
{(el, idx) => (
<li
style={'padding:2rem;border:1pt dashed violet;'}
{/* i want to pass trigger something in `el` */}
onClick={() => {
props.click({ idx: idx(), el: el });
}}>
{el}
</li>
)}
</For>
</ul>
);
};

const App: Component = () => {
return (
<List>
<Child>el 1</Child>
<Child>el 2</Child>
<Child>el 3</Child>
</List>
);
};
interface event {
idx: number;
el: JSXElement;
}

const List: FlowComponent<{ click: Setter<event> }, JSXElement[]> = (props) => {
return (
<ul>
<For each={props.children}>
{(el, idx) => (
<li
style={'padding:2rem;border:1pt dashed violet;'}
{/* i want to pass trigger something in `el` */}
onClick={() => {
props.click({ idx: idx(), el: el });
}}>
{el}
</li>
)}
</For>
</ul>
);
};

const App: Component = () => {
return (
<List>
<Child>el 1</Child>
<Child>el 2</Child>
<Child>el 3</Child>
</List>
);
};
idk what to pass to List and each Child
Alex Lohr
Alex Lohr2y ago
Ah, ok. Here's a nice pattern in solid to help you achieving that. You can use an array event handler union. There are two ways to do the parent-to-child communication; 1. is to use a FunctionComponent: make sure that el is (props) => JSX.Element and not an already rendered element very much like the one Child of <For>. 2. is context.
const [activeIndex, setActiveIndex] = createSignal(-1);
const isActive = createSelector(activeIndex);

<li onClick={[idx(), setActiveIndex]} aria-current={isActive(idx()) ? 'true' : undefined}>
const [activeIndex, setActiveIndex] = createSignal(-1);
const isActive = createSelector(activeIndex);

<li onClick={[idx(), setActiveIndex]} aria-current={isActive(idx()) ? 'true' : undefined}>
bokuno_disk_cord
bokuno_disk_cordOP2y ago
and i'd use List component like this?
const App: Component = () => {
return (
<List
items={[
Child,
Child,
Child,
Child,
Child,
Child,
Child,
Child,
Child,
]}
/>
);
};
const App: Component = () => {
return (
<List
items={[
Child,
Child,
Child,
Child,
Child,
Child,
Child,
Child,
Child,
]}
/>
);
};
thanks for your help 👍, that's the last question for today. i'll keep the thread open to see feedback from other members.
Unknown User
Unknown User2y ago
Message Not Public
Sign In & Join Server To View
Want results from more Discord servers?
Add your server