Skip to content

Instantly share code, notes, and snippets.

@majstudio
Last active July 16, 2018 18:13
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save majstudio/a821b7322a08ab0c25d2f3351f03b8da to your computer and use it in GitHub Desktop.
Save majstudio/a821b7322a08ab0c25d2f3351f03b8da to your computer and use it in GitHub Desktop.
React Component Automatic HTML Injection
<!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>
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
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
}
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