rax
Version:
A universal React-compatible render engine.
86 lines (76 loc) • 2.37 kB
JavaScript
import invokeFunctionsWithContext from './invokeFunctionsWithContext';
import { useState, useLayoutEffect } from './hooks';
import { isFunction } from './types';
import toArray from './toArray';
import getNearestParent from './vdom/getNearestParent';
let id = 0;
export default function createContext(defaultValue) {
const contextID = '_c' + id++;
// Provider Component
class Provider {
constructor() {
this.__contextID = contextID;
this.__handlers = [];
}
__on(handler) {
this.__handlers.push(handler);
}
__off(handler) {
this.__handlers = this.__handlers.filter(h => h !== handler);
}
// Like getChildContext but called in SSR
_getChildContext() {
return {
[contextID]: this
};
}
// `getValue()` called in rax-server-renderer
getValue() {
return this.props.value !== undefined ? this.props.value : defaultValue;
}
componentDidUpdate(prevProps) {
if (this.props.value !== prevProps.value) {
invokeFunctionsWithContext(this.__handlers, null, this.getValue());
}
}
render() {
return this.props.children;
}
}
function getNearestParentProvider(instance) {
return getNearestParent(instance, parent => parent.__contextID === contextID);
}
// Consumer Component
function Consumer(props, context) {
// Current `context[contextID]` only works in SSR
const [provider] = useState(() => context[contextID] || getNearestParentProvider(this));
let value = provider ? provider.getValue() : defaultValue;
const [prevValue, setValue] = useState(value);
if (value !== prevValue) {
setValue(value);
return; // Interrupt execution of consumer.
}
useLayoutEffect(() => {
if (provider) {
provider.__on(setValue);
return () => {
provider.__off(setValue);
};
}
}, []);
// Consumer requires a function as a child.
// The function receives the current context value.
const consumer = toArray(props.children)[0];
if (isFunction(consumer)) {
return consumer(value);
}
}
return {
Provider,
Consumer,
// `_contextID` and `_defaultValue` accessed in rax-server-renderer
_contextID: contextID,
_defaultValue: defaultValue,
__getNearestParentProvider: getNearestParentProvider,
};
}