Last active
July 23, 2020 11:49
-
-
Save jimthedev/b0fbccec1b1e43fb547900c5bbf3fe17 to your computer and use it in GitHub Desktop.
use web component hook for react
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { useCustomElement } from "./useCustomElement"; | |
// My regular react app that is using a web component / | |
// custom element inside it. | |
// Notice that we have some handlers, some state, etc. | |
function Component() { | |
const [txt, setTxt] = React.useState("init") | |
function handleClick() { | |
setTxt("clicked") | |
} | |
function handleLeave() { | |
setTxt("out") | |
} | |
function handleEnter() { | |
setTxt("enter") | |
} | |
return ( | |
<div> | |
<my-web-component | |
{...useCustomElement({ | |
onClick: handleClick, | |
onMouseLeave: handleLeave, | |
onMouseEnter: handleEnter, | |
title: "Title", | |
text: txt, | |
subtext: "I am some subtext" | |
})} | |
/> | |
</div> | |
) | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
export function useCustomElement(items) { | |
const ref = React.useRef(); | |
// Events are tricky in wc in react. We need to manually | |
// attach listeners. We can't rely on synthetic events. | |
React.useEffect(() => { | |
matchProperties( | |
{ | |
handler: (eventType, value) => { | |
return ref.current.addEventListener(eventType, value); | |
}, | |
property: (key, value) => { | |
return (ref.current[key] = value); | |
} | |
}, | |
items | |
); | |
function matchProperties(matchers, properties) { | |
for (let pair of Object.entries(items)) { | |
const [key, value] = pair; | |
if ( | |
key[0] === "o" && | |
key[1] === "n" && | |
key[2] == key[2].toUpperCase() | |
) { | |
if (typeof matchers.handler === "function") { | |
const eventType = key.substring(2, key.length).toLowerCase(); | |
matchers.handler(eventType, value); | |
} | |
} else { | |
if (typeof matchers.property === "function") { | |
matchers.property(key, value); | |
} | |
} | |
} | |
} | |
return () => { | |
matchProperties( | |
{ | |
handler: (eventType, value) => { | |
return ref.current.removeEventListener(eventType, value); | |
} | |
}, | |
items | |
); | |
}; | |
}, []); | |
// Deal with attributes that aren't events | |
const entries = Object.entries(items).filter( | |
([key, val]) => typeof items[key].indexOf !== "undefined" | |
); | |
const remainder = entries.reduce((a, item) => { | |
const [key, value] = item; | |
return { | |
...a, | |
[key]: value | |
}; | |
}, {}); | |
const result = { ref, ...remainder }; | |
return result; | |
} |
@sslotsky awesome. Thank you for the feedback!
- I will work on the on syntax you described. One thing I didn’t want to have to do was maintain a list of all possible events. The camel casing gets a bit strange and unfortunately mouseleave and other eventTypes aren’t camel cased. Tradeoffs exist but certainly we can play around with this. I agree my personal preference is still to find a way to use the onMouseLeave syntax but without an extensive camel casing map I am not sure how to make it happen.
2/3 You are correct, these should likely be props in some (or many) cases. I was thinking about adding in square bracket syntax alla Angular but perhaps there is a better solution here that involves data type checking and/or checking for the existence of a prop. Camel translation again becomes a question but I will look and see what others are doing here.
Perhaps the name of this thing should be useCustomElement instead of useWebComponent.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
This is really cool! A couple thoughts based on my experience with web components, as well as my experience using web components from React which is possibly more than I'd care to admit 😂
onMyEvent
, then to attach the listener they'd e.g.addEventListener('myEvent')
. Although your method might be easier to deal with. So of course this doesn't really improve things in any meaningful way but might make it feel more natural?setAttribute
you are setting an attribute and not a prop. You could see if the attribute is defined on the node withref.current.hasAttribute(key)
, and if that is true then callsetAttribute
. If it's false, I think you can just set a prop by doingref.current[key] = value
.ref.current[key] = value
. These types of data can't be set with an attribute anyway, so you may as well assume they are props.In case you haven't seen it, custom-elements-everywhere.com gives pretty good descriptions of how the different frameworks approach setting properties. They tend to do some combination of the above. I really like where you're going with this and I think it could be really useful to other React devs who find themselves working with WC.