Last active
October 16, 2018 08:08
-
-
Save stomita/576faa8979e0d4de5b41799d7d9f6cf9 to your computer and use it in GitHub Desktop.
Create React's new context API style component with legacy context
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* @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