Skip to content

Instantly share code, notes, and snippets.

@olov
Created September 27, 2017 20:58
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 olov/13d81abc47be511dec2c172edc15b59a to your computer and use it in GitHub Desktop.
Save olov/13d81abc47be511dec2c172edc15b59a to your computer and use it in GitHub Desktop.
import * as React from "react";
import { nextTick } from "../std/nexttick";
import { updated } from "../std/fridge";
import { shallowObjectEquals } from "../std/u";
const uniqueObject = {};
export class StatefulComponent<T, U> extends React.Component<T> {
protected readonly data: U;
private allowRender: boolean = true;
private dirty: boolean = false;
shouldComponentUpdate(nextProps: T): boolean {
return !shallowObjectEquals(this.props, nextProps);
}
asyncForceUpdate(): void {
if (this.dirty) {
return;
}
this.dirty = true;
this.allowRender = false;
try {
this.forceUpdate(); // may trigger an immediate sync call to this.render()
this.allowRender = true;
} catch (e) {
this.allowRender = true;
if (e !== uniqueObject) {
throw e;
}
nextTick(() => {
if (!this.dirty) {
return;
}
this.forceUpdate();
});
}
}
initializeData(data: Readonly<U>) {
(this as any).data = data;
}
updateData(partialData: Partial<U>) {
const updatedData = updated(this.data, partialData); // updated returns the same object if nothing changed
if (this.data !== updatedData) {
(this as any).data = updatedData;
this.asyncForceUpdate();
}
}
xrender(): JSX.Element | null {
return null;
}
render(): JSX.Element | null {
if (this.allowRender === false) {
throw uniqueObject;
}
this.dirty = false;
return this.xrender();
}
}
@olov
Copy link
Author

olov commented Sep 27, 2017

This is a stripped-down version that excludes a bunch of checks and some extra convenience methods. React 15 version that throws, for React 16 I instead return-cached-element in render.

In a component that extends StatefulComponent, xrender() is overridden. Its constructor sets up initial data using this.initializeData(). Updating data happens immediately, using updateData, and component is invalidated (needs a re-render). It's ok to do consecutive updateData calls and read this.data in between. Render will always be called async (thus the need for an async forceUpdate). sCU is only concerned about incoming properties, not internal state (this.data). Updating data means invalidate (so re-render).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment