Skip to content

Instantly share code, notes, and snippets.

@stomita
Last active October 16, 2018 08:08
Show Gist options
  • Save stomita/576faa8979e0d4de5b41799d7d9f6cf9 to your computer and use it in GitHub Desktop.
Save stomita/576faa8979e0d4de5b41799d7d9f6cf9 to your computer and use it in GitHub Desktop.
Create React's new context API style component with legacy context
/* @flow */
import EventEmitter from 'events';
import React, { type Node, type ComponentType } from 'react';
import PropTypes from 'prop-types';
/**
* Mimics new context API using legacy context
*/
type LegacyContextProvider<T> = ComponentType<{ value: T, children: Node }>;
type LegacyContextConsumer<T> = ComponentType<{ children: (value: T) => Node }>;
type LegacyContext<T> = {
Provider: LegacyContextProvider<T>,
Consumer: LegacyContextConsumer<T>,
};
/**
*
*/
class LegacyContextStore<T> extends EventEmitter {
_value: T;
constructor(value: T) {
super();
this.setMaxListeners(1000000); // increase listener num to 1 million
this._value = value;
}
set(value: T) {
if (!shallowEqual(this._value, value)) {
this._value = value;
this.emit('update', this._value);
}
}
subscribe(fn: (T) => void) {
fn(this._value);
this.addListener('update', fn);
}
unsubscribe(fn: (T) => void) {
this.removeListener('update', fn);
}
}
/**
*
*/
function createLegacyContextProvider<T>(contextKey: string): LegacyContextProvider<T> {
return class extends React.Component<{ value: T, children: Node }> {
_store: LegacyContextStore<T>;
static childContextTypes = {
[contextKey]: PropTypes.object,
}
constructor(props) {
super(props);
this._store = new LegacyContextStore(this.props.value);
}
getChildContext() {
return { [contextKey]: this._store };
}
componentDidMount() {
this._store.set(this.props.value);
}
componentDidUpdate() {
this._store.set(this.props.value);
}
componentWillUnmount() {
this._store.removeAllListeners();
}
render() {
return this.props.children;
}
};
}
/**
*
*/
function createLegacyContextConsumer<T>(
contextKey: string,
initialValue: T,
): LegacyContextConsumer<T> {
return class extends React.Component<{ children: (value: T) => Node }, { value: T }> {
static contextTypes = {
[contextKey]: PropTypes.object,
};
state = { value: initialValue };
componentDidMount() {
const store = this.context[contextKey];
if (store) {
store.subscribe(this.updateValue);
}
}
componentWillUnmount() {
const store = this.context[contextKey];
if (store) {
store.unsubscribe(this.updateValue);
}
}
updateValue = (value: T) => {
this.setState({ value });
}
render() {
return this.props.children(this.state.value) || <></>;
}
};
}
/**
* create legacy context which mimics new context API
*/
export function createLegacyContext<T>(initialValue: T): LegacyContext<T> {
const contextKey = `legacyContextStore_${Math.random().toString(16).substring(2)}`;
return {
Provider: createLegacyContextProvider(contextKey),
Consumer: createLegacyContextConsumer(contextKey, initialValue),
};
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment