Last active
July 16, 2018 18:13
-
-
Save majstudio/a821b7322a08ab0c25d2f3351f03b8da to your computer and use it in GitHub Desktop.
React Component Automatic HTML Injection
This file contains hidden or 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
<!DOCTYPE html> | |
<html lang="en"> | |
<head> | |
<meta charset="utf-8"> | |
<title>React App</title> | |
</head> | |
<script type="text/javascript"> | |
// Just random js for proof of dynamic eval | |
document.x = 12200; | |
</script> | |
<body> | |
// Important ! Never use shorthand <User /> or it will eat all other DOM elements after due to non support of non standard self-enclosing tags ! | |
<User firstName="Marc" lastName="Jacob" age="19" salute="()=>'Heywww'"></User> | |
<User firstName="Gab" lastName="Perry" age="26"></User> | |
//Same usage with a class component, note we can put any valid and compatible JS code in | |
<UserClass firstName="Nick" age="11" salute="()=>'omg '.toUpperCase() + document.x"></UserClass> | |
</body> | |
</html> |
This file contains hidden or 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 * as React from 'react' | |
import { injectCustomElement } from './Reactive' | |
import IUser, { User, UserClass } from './User' | |
// New automatic way directly in HTML doc, will consider all <User> tags and convert them to React comp on load | |
// Here the propType is IUser and the component type is User | |
injectCustomElement('User', IUser, User) | |
//Don't forget injectCustomElement returns an array of the freshly built React Components for further usage ! | |
const us = injectCustomElement('UserClass', IUser, UserClass) as UserClass[] | |
setInterval(() => { us[0].setMoney(us[0].state.money + 1) }, 1000) | |
// All is working !!! WOW |
This file contains hidden or 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 * as React from 'react' | |
import * as ReactDOM from 'react-dom' | |
type ReactComp<T> = React.StatelessComponent<T> | React.ComponentClass<T> | |
const enum RenderType { Replace, Append } | |
/// Used to automagically inject React component for custom tags in a HTML file | |
// Where P is your component Prop type, C is your react component type | |
export function injectCustomElement<P, C extends ReactComp<P>>(tagName: string, propType: new () => P, comp: C, renderType = RenderType.Replace): React.Component[] | |
{ | |
// Our prop to export; P must be a class with a default ctor | |
let prop: P; | |
// Select and find all the custom elements | |
// Convert the NodeList to array | |
const els = Array.from(document.querySelectorAll(tagName)) | |
let reactElem: React.Component; | |
const generatedElements: React.Component[] = []; | |
for (let el of els) | |
{ | |
prop = new propType() | |
// Build 2 arrays, one lowered because el.attributes lower them | |
const attr = Reflect.ownKeys(prop as any) as string[] | |
const attrLwrd = attr.map((e: string) => e.toLowerCase()) | |
// Loop and match properties value | |
for (let i = 0, atts = el.attributes, n = atts.length; i < n; i++) | |
{ | |
const index = attrLwrd.indexOf(atts[i].name) | |
if (index !== -1) | |
{ | |
console.log(attr[index] + ' = ' + atts[i].value) | |
const type = typeof Reflect.get(prop as any, attr[index]) | |
// Depending of the type we are expecting for the attribute, we will eval() the attribute value expression if it's supposed to be a non-primitive | |
// tslint:disable-next-line | |
Object.defineProperty(prop, attr[index], { value: ((type === 'object' || type === 'function') ? eval(atts[i].value) : atts[i].value) }) | |
} | |
} | |
if (renderType === RenderType.Append) | |
{ | |
// Inject the JSX in a fictive inner divx, preserving existing HTML childs nodes | |
const divx = document.createElement('divx') | |
el.appendChild(divx) | |
el = divx | |
} | |
reactElem = ReactDOM.render(React.createElement(comp, prop, null), el as HTMLElement) as React.Component | |
generatedElements.push(reactElem) | |
} | |
return generatedElements | |
} |
This file contains hidden or 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 * as React from 'react' | |
// Example interface model for a prop type your component need | |
// Note that we cannot use an interface here because we need to default the object with the default ctor for the injection to work | |
export default class IUser | |
{ | |
public firstName = '' | |
public lastName = '' | |
public age = 0 | |
public salute: ()=>string = (() => "Hey!") | |
} | |
//Example Stateless React Component | |
export function User(user: IUser) | |
{ | |
return ( | |
<> | |
<p>My name is {user.firstName + ' ' + user.lastName} and I'm {user.age} years old !</p> | |
<p> {user.salute()} </p> | |
</> | |
); | |
} | |
// Example React Class Component<P, S> | |
export class UserClass extends React.Component<IUser, { money: number }> | |
{ | |
constructor(props: IUser) | |
{ | |
super(props) | |
this.state = { money: 0 } | |
} | |
public render() | |
{ | |
const user: IUser = this.props as IUser | |
return ( | |
<> | |
{User(user)} | |
<p>I have {this.state.money} $</p> | |
</> | |
); | |
} | |
public setMoney = (m: number) => this.setState({ money: m }) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment