react-simple-ssr/SSRComponent.js

130 lines
3.6 KiB
JavaScript

// Component capable of saving & restoring state during render
// (c) Vitaliy Filippov 2019+
// Version: 2021-08-07
// License: Dual-license MPL 1.1+ or GNU LGPL 3.0+
// NOTE: Child components of the same class should have unique `key`s
import React from 'react';
export default class SSRComponent extends React.PureComponent
{
constructor(props)
{
super(props);
if (props.store && props.store.state)
{
this.unserializeState(props.store.state);
delete props.store.state;
}
else
{
this.state = {};
this.init(props);
}
}
init(props)
{
}
static serializeStore(store)
{
store = { ...store };
if (store.instance)
{
store.state = store.instance.serializeState();
delete store.instance;
}
if (store.children)
{
store.children = { ...store.children };
for (const className in store.children)
{
store.children[className] = { ...store.children[className] };
for (const key in store.children[className])
{
store.children[className][key] = SSRComponent.serializeStore(store.children[className][key]);
}
}
}
return store;
}
serializeState()
{
return { ...this.state };
}
unserializeState(state)
{
this.state = state;
}
passStore(children)
{
return React.Children.map(children, (child) =>
{
if (child && (child.type instanceof Object) && (child.type.prototype instanceof SSRComponent))
{
const className = child.type.name;
this.props.store.idx[className] ||= { num: 0, keys: {} };
let key;
if (child.props.key)
key = ':'+child.props.key;
else
{
this.props.store.idx[className].num++;
key = '['+this.props.store.idx[className].num;
}
this.props.store.idx[className].keys[key] = true;
let chstore = (this.props.store.children ||= {});
chstore = (chstore[className] ||= {});
chstore = (chstore[key] ||= {});
return React.cloneElement(child, {
store: chstore,
children: child.props && child.props.children ? this.passStore(child.props.children) : undefined,
});
}
else if (child && child.props && child.props.children)
{
return React.cloneElement(child, {
children: this.passStore(child.props.children),
});
}
return child;
});
}
render()
{
if (this.props.store)
{
this.props.store.instance = this;
this.props.store.idx = {};
}
let children = this.doRender();
if (this.props.store)
{
children = this.passStore(children);
// Clear unused keys
for (let className in this.props.store.idx)
{
for (let key in this.props.store.children[className])
{
if (!this.props.store.idx[className].keys[key])
{
delete this.props.store.children[className][key];
}
}
}
delete this.props.store.idx;
}
return children;
}
doRender()
{
}
}