"Keo" is the Vietnamese translation for glue.
Plain functions for a more functional Deku approach to creating stateless React components, with functional goodies such as compose, memoize, etc... for free.
As Shadow DOM is part of the webcomponents suite, there is a useful polyfill which you can use for wider browser support — please refer to their documentation for a list of supported browsers.
Note: Works in React >=15.0.2
— see issue and associated PR.
By using function composition in Keo, you are able to add additional behaviour to your functions by using compose
or pipe
– in this case we are using the bundled shadow
function to enable Shadow DOM in React.
In order to use Shadow DOM you need to compose
your render
function, which means importing compose
— or pipe
— from Ramda, along with shadow
from Keo and then composing your render
function.
import { compose } from 'ramda';
import { stitch, shadow } from 'keo';
// ...
const render = compose(shadow(), ({ props }) => {
return (
<section className="greeting">
<h1>Hello ${props.name}</h1>
</section>
);
});
Once you have the composition setup with shadow
you should find your DOM rendering with a shadow root. Keo takes your component's root element and appends that to the DOM tree; it then creates a shadow root and renders your component's children
inside.
Note: If you have only one child then your component will be rendered directly beneath the shadow root, otherwise due to a well-known React limitation, a span
will be the immediate descendent of the shadow root, and your component will be rendered within that span
.
You should see the following when inspecting the DOM:
<section class="greeting resolved">
#shadow-root (open)
<h1>Hello Adam</h1>
</section>
Any event listeners and other props on your root section
element will be transferred across and therefore work as you would expect.
You may have noticed that when composing your render
function that you're invoking shadow
instead of simply passing it in – that is because the shadow
function is curried and accepts two arguments: the first being a string
or an array
of stylesheets, and the second being the result of your render
function which compose
— or pipe
— will handle for you.
Therefore to pass in stylesheets to be used with your component, simply pass in a path to your CSS document(s):
const render = compose(shadow('/css/greeting.css'), ({ props }) => {
return (
<section className="greeting">
<h1>Hello ${props.name}</h1>
</section>
);
});
You should now see the following DOM structure with the style
element applied next to your component:
<section class="greeting resolving">
#shadow-root (open)
<h1>Hello Adam</h1>
<style type="text/css" />
</section>
As the stylesheets are downloaded asynchronously, there is a small period of time where your component will be rendered without the styles applied — this is known as a flash of unstyled content (FOUC). For these purposes it may be useful to hide your component until the styles have been applied — that is the reason for Keo appending resolved
or resolving
to your component's root element:
<section class="greeting resolving">
#shadow-root (open)
<h1>Hello Adam</h1>
<style type="text/css" />
</section>
When the component has the class name resolving
then it may be wise to hide the element, or at least let users know the component hasn't yet finished rendering. However once the class name is resolved
then the component should have all the styles it needs to be displayed beautifully.
It's been mentioned that you could also use pipe
to place the shadow
function at the end for readability purposes — whichever you choose is entirely up to you — but don't forget to be consistent across components!
import { pipe } from 'ramda';
import { stitch, shadow } from 'keo';
// ...
const render = pipe(({ props }) => {
return (
<section>
<h1>Hello ${props.name}</h1>
</section>
);
}, shadow(['/css/greeting.css', '/css/headers.css']));