How to pass event from component to nested function? - javascript

I've got two splitted functions and I need to pass a value from one to the other, which I'm doing like shown below. What is the difference between const handleClick = icon.onClick(category) and const handleClick = () => icon.onClick(category)?
And how do I pass the event from the component to the handleClick() function?
export const useCategories = () => {
const handleClick = (category, something) => {
event.stopPropagation() // <-- 3. How to get event?
console.log(category, something) // <-- 4. Get every value
}
return {
icon: {
onClick: (category) => handleClick(category, 'anything') // <-- 2. add second var value
}
}
}
export const Categories = () => {
const { icon } = useCategories()
return (
<div>
{categories.map((category) => {
const handleClick = icon.onClick(category) // <-- 1. pass category value
return <Icon onClick={handleClick} />)}
}
</div>
)
}

You'll need to proxy the event object through on all click handlers. I like to use curried functions to make attaching the click handler a little simpler. Don't forget to add a react key to the mapped icons.
export const useCategories = () => {
const handleClick = (event, category, something) => {
event.stopPropagation();
console.log(category, something);
};
return {
icon: {
// curried function to receive category and return onClick handler
onClick: category => event => handleClick(event, category, 'anything'),
}
}
}
export const Categories = () => {
const { icon } = useCategories();
return (
<div>
{categories.map((category, index) => (
<Icon
key={index}
onClick={icon.onClick(category)} // <-- set category
/>
)
</div>
);
}

The onevent handlers are properties on certain DOM elements to manage how that element reacts to events.
When the event handler is specified as an HTML attribute, the specified code is wrapped into a function with the following parameters:
event — for all event handlers except onerror.
event, source, lineno,colno, and error for the onerror event handler.
Note that the event parameter actually contains the error
message as a string.
When the event handler is invoked, the this keyword inside the handler is set to the DOM element on which the handler is registered. For more details, see, see the this keyword documentation.
if you want more see, this
but in your code, you need to add this
<Icon onClick={(event) => icon.onClick(event, category, ...rest)} />

As you want to pass the event and category to the click handler, modify the function passed to onClick to pass the args.
export const useCategories = () => {
const handleClick = (event, ...rest) => {
event.stopPropagation();
console.log(rest);
};
return {
icon: {
onClick: (event, category) => handleClick(event, category, 'anything'),
},
};
};
export const Categories = () => {
const { icon } = useCategories();
return (
<div>
{categories.map((category) => {
return <Icon onClick={(event) => icon.onClick(event, category)} />;
})}
</div>
);
};

When you dont need to pass parametrs you use
onClick={func}
When you want to pass parametrs you use
onClick={() => func(someParmeter)}
To pass the event simply write
onClick={e => func(e)}

Related

How to perform multiple actions when clicking on a button component?

I created a button in solid-js and I would like that when this button is clicked, a page is opened and the counter goes to zero. To perform these operations, I have already created two functions. I would like to ensure that when clicking this button that these two functions are called asynchronously. So I wrote the following code:
<button
onClick={[toggle, setNullCount]}
>
with functions to call toggle and setNullCount. However, I realized that when I click on the button, only the first declared function is called and I don't know how to allow two functions to be called on click.
Here is a solution
onClick={() => {toggle(); setNullCount()}}
when you want assign more than one action to trigger you must create a function which handles methods that will be performed as effect.
You need to create a single handler function that calls both handlers:
<button
onClick={() => {
toggle();
setNullCount();
}}
>
A wrapper function is not equivalent to adding multiple listeners and it is not always feasible, i.e when you want to handle each events differently.
An alternative could be adding listeners to the element using its reference:
import { onCleanup, onMount } from 'solid-js';
import { render } from 'solid-js/web'
const App = () => {
let el: HTMLButtonElement | undefined;
const handleOne = (event: any) => {
console.log(`Event One`);
};
const handleTwo = (event: any) => {
console.log(`Event Two`);
};
onMount(() => {
el?.addEventListener('click', handleOne);
el?.addEventListener('click', handleTwo);
});
onCleanup(() => {
el?.removeEventListener('click', handleOne);
el?.removeEventListener('click', handleTwo);
});
return (
<div>
<button ref={el}>Click</button>
</div>
)
};
render(App, document.body);
You can see the live demo: https://playground.solidjs.com/anonymous/86afc8e3-3574-40ed-88f7-2e5b467f6b9a
Element is guaranteed to exist inside onMount and onCleanup effects but I didn't want to suppress the type system by a non-null assertion so used optional chaining operator, ?.
Another alternative could be event delegation:
import { onCleanup, onMount } from 'solid-js';
import { render } from 'solid-js/web'
const App = () => {
const handleOne = (event: any) => {
console.log(`Event One`);
};
const handleTwo = (event: any) => {
console.log(`Event Two`);
};
onMount(() => {
document.body.addEventListener('click', handleOne);
document.body.addEventListener('click', handleTwo);
});
onCleanup(() => {
document.body.removeEventListener('click', handleOne);
document.body.removeEventListener('click', handleTwo);
});
return (
<div>
<button>Click</button>
</div>
)
};
render(App, document.body);
Click the link to see the live demo: https://playground.solidjs.com/anonymous/434364d4-c467-427f-8709-3e10557e0b9e
A third alternative could be using so called on:weirdEvent syntax:
import { render } from 'solid-js/web'
const App = () => {
const handleOne = (event: any) => {
console.log(`Event One`);
};
const handleTwo = (event: any) => {
console.log(`Event Two`);
};
return (
<div>
<button on:click={handleOne} on:click={handleTwo}>Click</button>
</div>
)
};
render(App, document.body);
For any other events, perhaps ones with unusual names, or ones you wish not to be delegated, there are the on namespace events. This attribute adds an event listener verbatim.
https://www.solidjs.com/docs/latest/api#on___oncapture___
You can find the live demo: https://playground.solidjs.com/anonymous/30c32d5b-3185-45ab-987d-15f2bf9c8f98
A fourth alternative could be using a custom directive. A custom directive receives the element when the element gets created in the DOM. So, you can attach the listener on the element itself or use event delegation. I will go with the former one here:
import { Accessor, onCleanup } from 'solid-js';
import { render } from 'solid-js/web';
interface Handlers {
handlerOne: (event: MouseEvent) => void;
handlerTwo: (event: MouseEvent) => void;
}
export const multipleHandlers = (el: HTMLButtonElement, args: Accessor<Handlers>) => {
el.addEventListener("click", args().handlerOne);
el.addEventListener("click", args().handlerTwo);
onCleanup(() => el.removeEventListener("click", args().handlerOne));
onCleanup(() => el.removeEventListener("click", args().handlerTwo));
}
declare module "solid-js" {
namespace JSX {
interface Directives {
multipleHandlers: Handlers;
}
}
}
const App = () => {
const handlerOne = (event: any) => {
console.log(`Event One`);
};
const handlerTwo = (event: any) => {
console.log(`Event Two`);
};
return (
<div>
<button use:multipleHandlers={{ handlerOne, handlerTwo }}>Click</button>
</div>
)
}
render(App, document.body);
https://playground.solidjs.com/anonymous/d6cbfe71-d657-4749-9126-a5fc5984a334
Explicit being better than implicit, I advice you to use any of the above methods over a custom directive. Custom directives could be a good alternative only when you find yourself in need to add multiple listeners to the same element frequently.

Why is event object not accessable once I wrap two functions in anonymous function in submit handler?

So I have wrapped two functions in a onClick handler like so:
const PlannerDetailsFooter = (props) => {
const [checked, setChecked] = useState(false);
const handleChange = () => {
setChecked((prev) => !prev);
};
return (
<div className="details-footer w-full p-4 bottom-0 absolute border-t-1">
{props.emailConfig.isHidden ? null :
<button id="mail-button"
onClick={() => {props.emailHandler(); handleChange()}}
>{props.emailConfig.buttonText}</button>
</div>
)
}
This is one of the two called functions which is located in the parent component
const emailHandler = (event) => {
if (event.target.id === "mail-button") {
setEmailConfig({...emailConfig, isHidden: false, buttonText: 'Send Now'})
} else {
setEmailConfig(event.target.value)
}
}
Now I get the error
Cannot read properties of undefined (reading 'target')
Using this solution the handler in the parent component can access the event object:
onClick={props.emailHandler}
When you use onClick={props.emailHandler} you are binding emailHandler to onclick and it will receive the parameters passed by onclick.
Now, when you use () => {props.emailHandler(); ...} You are not passing the parameters to emailHandler. To fix this you can do (e) => {props.emailHandler(e); ...}

How to memoize a high order function while using useCallback?

Using React Hooks, when we want to memoize the creation of a function, we have the useCallback hook. So that we have:
const MyComponent = ({ dependancies }) => {
const memoizedFn = useCallback(() => {
/* ... */
}, [dependancies]);
return <ChildComponent onClick={memoizedFn} />;
}
My question is, how do we memoize the values of a high order function in a useCallback hook such as:
const MyComponent => ({ dependancies, anArray }) => {
const memoizedFnCreator = useCallback((id) => () => {
/* ... */
}, [dependancies]);
/*
* How do we make sure calling "memoizedFnCreator" does not result in
* the ChildComponent rerendering due to a new function being created?
*/
return (
<div>
{anArray.map(({ id }) => (
<ChildComponent key={id} onClick={memoizedFnCreator(id)} />
))}
</div>
);
}
Instead of passing a "creator-function" in the HoC you can pass down a function which takes in a id as argument and let the ChildComponent create it's own click handler
In the code example below notice that the onClick in the MyComponent no longer create a unique function, but reuses the same function across all the mapped elements, however the ChildComponent creates a unique function.
const ChildComponent = ({ itemId, onClick }) => {
// Create a onClick handler when calls `onClick` with the item's id
const handleClick = useCallback(() => {
onClick(itemId)
}, [onClick, itemId])
return <button onClick={handleClick}>Click me</button>
}
const MyComponent = ({ dependancies, anArray }) => {
// Memoize a function which takes in the id and performs some action
const handleItemClick = useCallback((id) => {
/* ... */
}, [dependancies]);
return (
<div>
{anArray.map(({ id }) => (
<ChildComponent key={id} itemId={id} onClick={handleItemClick} />
))}
</div>
);
}
Depending on what you are trying to achieve, the optimal solution might be entirely different, but here's one way to go about it:
const MyComponent = ({ dependencies, array }) => {
// create a memoized callbacks cache, where we will store callbacks.
// This cache will reset every time dependencies change.
const callbacks = useMemo(() => ({}), [dependencies]);
const createCallback = useCallback(
id => {
if (!callbacks[id]) {
// Cache the callback here...
callbacks[id] = () => {
/* ... */
};
}
// ...and return it.
return callbacks[id];
},
[dependencies, callbacks]
);
return (
<div>
{array.map(({ id }) => (
<ChildComponent key={id} onClick={createCallback(id)} />
))}
</div>
);
};
Since the cache resets as dependencies change, your callbacks will update accordingly.
If it were me, I could avoid higher order function as much as possible. In this case, I would have something like
const MyComponent => ({ dependancies, anArray }) => {
const memoizedFnCreator = useCallback((id, event) => {}, [dependancies]);
return (
<div>
{anArray.map(({ id }) => (
<ChildComponent key={id} onClick={(event) => memoizedFnCreator(id,event)}/>
))}
</div>
);
}

How to call two onClick events from different places

I have a button that is going to have a onClick that is hardcoded and another one that however uses the component, can create his custom onClick. I don't believe this is a duplicated since all the questions i was able to found advise to use both onClick's in a hardcoded way, that can't be changed when using the Button component.
This the component:
const Button = props => {
const { children, toggle, ...other } = props;
const [pressed, setPressed] = useState(true);
const renderPressedButton = e => {
setPressed(!pressed);
onClick(); //here onClick should be called
};
return (
<StyledButton
toggle={toggle}
pressed={toggle ? pressed : null}
onClick={toggle && renderPressedButton}
{...other}>
{children}
</StyledButton>
);
};
I have also tried this function on the onClick event:
const onClickHandler = () => {
renderPressedButton();
onClick();
}
And using the component i would have my custom onClick
const anotherClickHandler = () => {
console.log('Hey, this is a function outside Button component');
}
<Button onClick={anotherClickHandler}>Button</Button>
What i have tried is calling the onClick(); inside the renderPressedButton from the first onClick but it did not work. The second onClick(anotherClickHandler) is called but not the first one(renderPressedButton).
What i'm doing wrong?
I have found the solution.
On the main Button:
const Button = props => {
const { onClick } = props; //first i add onClick as a prop
const [pressed, setPressed] = useState(true);
const renderPressedButton = () => {
setPressed(!pressed);
if (onClick) {//then i check, if theres an onClick, execute it
onClick();
}
};
return (
<StyledButton
pressed={pressed}
{...props}
onClick={renderPressedButton}
/>
);
};
And when using the Button:
<Button onClick={justASimpleOnClickHandler}>Toggle</Button>
This way i can have my main onClick function and another one when using my component.

React custom synthetic event

I would like to create a custom synthetic event so that I can conditionally attach a handler (either a touchstart or click), based on a configuration option
However, I cannot seem to find any information on how this can be done
Ideally I would like the below (the onTap attribute)
<Button onTap={someHandler} title="Register" />
And then the onTap would attach the handler either on touchstart or click or any event I define in a config
Is this possible?
Is it possible to define a custom attribute that will hook on every component?
Regards
You could try something like this:
const W = (() => {
// all elements supported by React
const names = 'a|abbr|address|area|article|aside|audio|b|base|bdi|bdo|big|blockquote|body|br|button|canvas|caption|cite|code|col|colgroup|data|datalist|dd|del|details|dfn|dialog|div|dl|dt|em|embed|fieldset|figcaption|figure|footer|form|h1|h2|h3|h4|h5|h6|head|header|hgroup|hr|html|i|iframe|img|input|ins|kbd|keygen|label|legend|li|link|main|map|mark|menu|menuitem|meta|meter|nav|noscript|object|ol|optgroup|option|output|p|param|picture|pre|progress|q|rp|rt|ruby|s|samp|script|section|select|small|source|span|strong|style|sub|summary|sup|table|tbody|td|textarea|tfoot|th|thead|time|title|tr|track|u|ul|var|video|wbr|circle|clipPath|defs|ellipse|g|image|line|linearGradient|mask|path|pattern|polygon|polyline|radialGradient|rect|stop|svg|text|tspan'.split('|')
const res = {}
for (const El of names) {
res[El] = ({ onTap, ...props }) => {
onTap = onTap || x=>x
props.onClick = props.onClick || x => x
props.onTouchStart = props.onTouchStart || x => x
<El {...props} onClick={(...args) => {onTap(...args); props.onClick(...args)} onTouchStart={(...args) => {onTap(...args); props.onTouchStart(...args)} />
}
}
return res;
})()
<W.button onTap={() => alert('hi')} />
This will add the onTap handler to both the onClick and onTouchStart events of any element.
A similar technique can be used to wrap composite components.
To wrap every component, you need to wrap React.createElement.
Warning: I make no guarantees about if this will work. It is probably a very bad idea, and should not be used in a library.
const _ce = React.createElement.bind(React)
React.createElement = (name, props, ...args) => {
if (!props) {
return _ce(name, props, ...args)
}
const { onTap, ...newProps } = props
if (onTap) {
if (props.onClick) {
newProps.onClick = (...args) => {
props.onClick(...args)
onTap(...args)
}
} else {
newProps.onClick = onTap
}
if (props.onTouchStart) {
newProps.onTouchStart = (...args) => {
props.onTouchStart(...args)
onTap(...args)
}
} else {
newProps.onTouchStart = onTap
}
}
return _ce(name, newProps, ...args)
}

Categories