Skip to content

Instantly share code, notes, and snippets.

@romgrk
Last active December 12, 2023 06:41
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save romgrk/6c09f15d98327ea43074d9bba086b529 to your computer and use it in GitHub Desktop.
Save romgrk/6c09f15d98327ea43074d9bba086b529 to your computer and use it in GitHub Desktop.
/*
* Skipping over internal reactivity but this would be the gist of how to build a component that doesn't
* use react but still allows customization via user-supplied react-based render functions.
*
* Why? Because react is way to slow for use-cases like virtualization+scrolling. The whole cycle (state-update
* then re-render then vdom diffing then dom update) has too many unavoidable costs. Being able to go from
* state-update to dom-update directly is unbeatable.
*
* It uses react portals, so even though it manages its own HTML structure, it contains inside itself parts that
* are rendered & managed by react. And portals ensure there is full react compatibility, including the Context API,
* event-bubbling, and useEffect unmount callbacks. Portals are efficient enough to create large numbers of them.
*/
type Target = {
node: HTMLElement
jsx: React.ReactNode
}
/* the component itself */
class Grid {
constructor(root, setTargets) {
this.root = root
this.setTargets = setTargets
}
setProps(props) {
this.props = props
this.render()
}
render() {
/* this is just the initial render but the component would manage its own internal reactivity & updates
* in whichever way it sees fit to do it */
const targets = []
props.rows.forEach((row) => {
const rowNode = document.createElement('div')
this.root.appendChild(rowNode)
props.cols.forEach((col) => {
const cellNode = document.createElement('div')
row.appendChild(cellNode)
if (col.render) {
/* external rendering, let react render whenever it finds time to do it */
targets.push({ node: cellNode, jsx: col.render(/* ... */) })
} else {
/* internal rendering, use DOM API directly */
cellNode.innerText = row[col.field]
}
})
})
this.setTargets(targets)
}
dispose = () => { /* ... */ }
}
function Wrapper /* memoized ideally */ (props) {
const [targets, setTargets] = useState([])
let component
useEffect(() => (component = new Grid(ref.current, setTargets)).dispose, [])
useEffect(() => component.setProps(props), [props])
return (
<>
<div ref={ref} />
<>
{targets.map(({ node, jsx }) =>
<React.Fragment>
{createPortal(jsx, node)}
</React.Fragment>
}
</>
)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment