Skip to content

Commit

Permalink
Merge pull request #1306 from gaearon/12.9
Browse files Browse the repository at this point in the history
Update Context Provider, fixes #1207
  • Loading branch information
theKashey authored Jul 23, 2019
2 parents 5bac044 + e228aad commit 9e666ae
Show file tree
Hide file tree
Showing 9 changed files with 209 additions and 65 deletions.
6 changes: 4 additions & 2 deletions examples/styled-components/src/Counter.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ const ComponentA = () => {
const [state] = useState('A');
return (
<div>
{state}-{value}
{state}-{value}-
<TimerContext.Consumer>{v => v}</TimerContext.Consumer>
</div>
);
};
Expand All @@ -17,7 +18,8 @@ const ComponentB = () => {
const value = useContext(TimerContext);
return (
<div>
{state}-{value}
{state}-{value}-
<TimerContext.Consumer>{v => v}</TimerContext.Consumer>
</div>
);
};
Expand Down
22 changes: 22 additions & 0 deletions examples/styled-components/src/Spring.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,36 @@
import { animated, useSpring } from 'react-spring';
import React, { useCallback, useState } from 'react';
import Counter from './Counter';

const context = React.createContext('test1');

const Test = () => {
const v = React.useContext(context);

return (
<div>
--{v}-- ##<Counter />
</div>
);
};

export function SpringTest() {
const [thingDone, toggleThingDone] = useState(false);
const doTheThing = useCallback(() => toggleThingDone(!thingDone), [thingDone]);

const fader = useSpring({ opacity: thingDone ? 1 : 0 });

const v = React.useContext(context);

return (
<React.Fragment>
{v}
<context.Provider value={24}>
<Test />
</context.Provider>
<context.Provider value="test2">
<Test />
</context.Provider>
<animated.h1 style={fader}>You did the thing!</animated.h1>
<button type="button" onClick={doTheThing}>
{thingDone ? 'Undo The Thing' : 'Do The Thing'}
Expand Down
2 changes: 2 additions & 0 deletions src/configuration.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ const configuration = {
// Disable "hot-replacement-render"
disableHotRenderer: false,

// @private
integratedComparator: false,
// @private
integratedResolver: false,

Expand Down
2 changes: 1 addition & 1 deletion src/errorReporter.js
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ const mapError = ({ error, errorInfo, component }) => (
<React.Fragment>
<p style={{ color: 'red' }}>
{errorHeader(component, errorInfo && errorInfo.componentStack)}{' '}
{error.toString ? error.toString() : error.message || 'undefined error'}
{error.toString ? error.toString() : (error && error.message) || 'undefined error'}
</p>
{errorInfo && errorInfo.componentStack ? (
<div>
Expand Down
105 changes: 59 additions & 46 deletions src/reactHotLoader.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import logger from './logger';

import { preactAdapter } from './adapters/preact';
import { updateContext, updateForward, updateLazy, updateMemo } from './reconciler/fiberUpdater';
import { resolveType } from './reconciler/resolver';
import { resolveSimpleType, resolveType } from './reconciler/resolver';
import { hotComponentCompare } from './reconciler/componentComparator';

const forceSimpleSFC = { proxy: { pureSFC: true } };
Expand Down Expand Up @@ -133,6 +133,7 @@ const reactHotLoader = {
},

patch(React, ReactDOM) {
let typeResolver = resolveType;
/* eslint-disable no-console */
if (ReactDOM && ReactDOM.setHotElementComparator) {
ReactDOM.setHotElementComparator(hotComponentCompare);
Expand All @@ -142,69 +143,81 @@ const reactHotLoader = {

reactHotLoader.IS_REACT_MERGE_ENABLED = true;
configuration.showReactDomPatchNotification = false;
configuration.integratedComparator = true;

if (ReactDOM.setHotTypeResolver) {
configuration.integratedResolver = true;
typeResolver = resolveSimpleType;
ReactDOM.setHotTypeResolver(resolveType);
}
}

if (!configuration.integratedResolver) {
/* eslint-enable */
if (!React.createElement.isPatchedByReactHotLoader) {
const originalCreateElement = React.createElement;
// Trick React into rendering a proxy so that
// its state is preserved when the class changes.
// This will update the proxy if it's for a known type.
React.createElement = (type, ...args) => originalCreateElement(resolveType(type), ...args);
React.createElement.isPatchedByReactHotLoader = true;
}
// PATCH REACT METHODS

if (!React.cloneElement.isPatchedByReactHotLoader) {
const originalCloneElement = React.cloneElement;

React.cloneElement = (element, ...args) => {
const newType = element.type && resolveType(element.type);
if (newType && newType !== element.type) {
return originalCloneElement(
{
...element,
type: newType,
},
...args,
);
}
return originalCloneElement(element, ...args);
};
/* eslint-enable */
if (!React.createElement.isPatchedByReactHotLoader) {
const originalCreateElement = React.createElement;
// Trick React into rendering a proxy so that
// its state is preserved when the class changes.
// This will update the proxy if it's for a known type.
React.createElement = (type, ...args) => originalCreateElement(typeResolver(type), ...args);
React.createElement.isPatchedByReactHotLoader = true;
}

React.cloneElement.isPatchedByReactHotLoader = true;
}
if (!React.cloneElement.isPatchedByReactHotLoader) {
const originalCloneElement = React.cloneElement;

React.cloneElement = (element, ...args) => {
const newType = element.type && typeResolver(element.type);
if (newType && newType !== element.type) {
return originalCloneElement(
{
...element,
type: newType,
},
...args,
);
}
return originalCloneElement(element, ...args);
};

if (!React.createFactory.isPatchedByReactHotLoader) {
// Patch React.createFactory to use patched createElement
// because the original implementation uses the internal,
// unpatched ReactElement.createElement
React.createFactory = type => {
const factory = React.createElement.bind(null, type);
factory.type = type;
return factory;
};
React.createFactory.isPatchedByReactHotLoader = true;
}
React.cloneElement.isPatchedByReactHotLoader = true;
}

if (!React.Children.only.isPatchedByReactHotLoader) {
const originalChildrenOnly = React.Children.only;
// Use the same trick as React.createElement
React.Children.only = children => originalChildrenOnly({ ...children, type: resolveType(children.type) });
React.Children.only.isPatchedByReactHotLoader = true;
}
if (!React.createFactory.isPatchedByReactHotLoader) {
// Patch React.createFactory to use patched createElement
// because the original implementation uses the internal,
// unpatched ReactElement.createElement
React.createFactory = type => {
const factory = React.createElement.bind(null, type);
factory.type = type;
return factory;
};
React.createFactory.isPatchedByReactHotLoader = true;
}

if (!React.Children.only.isPatchedByReactHotLoader) {
const originalChildrenOnly = React.Children.only;
// Use the same trick as React.createElement
React.Children.only = children =>
originalChildrenOnly({
...children,
type: typeResolver(children.type),
});
React.Children.only.isPatchedByReactHotLoader = true;
}

// PATCH REACT HOOKS

if (React.useEffect && !React.useEffect.isPatchedByReactHotLoader) {
React.useEffect = hookWrapper(React.useEffect);
React.useLayoutEffect = hookWrapper(React.useLayoutEffect);
React.useCallback = hookWrapper(React.useCallback);
React.useMemo = hookWrapper(React.useMemo);

// transform context for useContext
const { useContext } = React;
React.useContext = (context, ...args) => useContext(typeResolver(context), ...args);
}

// reactHotLoader.reset()
Expand Down
6 changes: 6 additions & 0 deletions src/reconciler/componentComparator.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { PROXY_KEY, UNWRAP_PROXY } from '../proxy';
import { resolveType } from './resolver';
import logger from '../logger';
import configuration from '../configuration';
import { updateLazy } from './fiberUpdater';

const getInnerComponentType = component => {
const unwrapper = component[UNWRAP_PROXY];
Expand Down Expand Up @@ -153,10 +154,15 @@ const compareComponents = (oldType, newType, setNewType, baseType) => {
}

if (isLazyType({ type: oldType })) {
updateLazy(oldType, newType);
// no need to update
// setNewType(newType);
return defaultResult;
}

if (isContextType({ type: oldType })) {
// update provider
setNewType(newType);
return defaultResult;
}

Expand Down
18 changes: 12 additions & 6 deletions src/reconciler/fiberUpdater.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,9 @@ import { resolveType } from './resolver';

const lazyConstructor = '_ctor';

export const updateLazy = (target, type) => {
const ctor = type[lazyConstructor];
if (target[lazyConstructor] !== type[lazyConstructor]) {
// just execute `import` and RHL.register will do the job
ctor();
}
const patchLazyContructor = target => {
if (!target[lazyConstructor].isPatchedByReactHotLoader) {
const ctor = target[lazyConstructor];
target[lazyConstructor] = () =>
ctor().then(m => {
const C = resolveType(m.default);
Expand All @@ -39,6 +35,16 @@ export const updateLazy = (target, type) => {
}
};

export const updateLazy = (target, type) => {
const ctor = type[lazyConstructor];
if (target[lazyConstructor] !== type[lazyConstructor]) {
// just execute `import` and RHL.register will do the job
ctor();
}
patchLazyContructor(target);
patchLazyContructor(type);
};

export const updateMemo = (target, { type }) => {
target.type = resolveType(type);
};
Expand Down
48 changes: 38 additions & 10 deletions src/reconciler/resolver.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,21 +11,26 @@ import configuration, { internalConfiguration } from '../configuration';

const shouldNotPatchComponent = type => isTypeBlacklisted(type);

export function resolveType(type, options = {}) {
// fast return
if (!isCompositeComponent(type) || isProxyType(type)) {
return type;
}
export function resolveUtility(type) {
// all "utility" types are resolved to their __initial__ shapes
// that enables to never change reference to them, and gives the ability to maintain React Tree on HMR

const element = { type };
// all operations could be skipped with react-hot-dom enabled

// fast meta
if (typeof element === 'object') {
if (typeof type === 'object') {
if (configuration.integratedComparator) {
return type;
}
const element = { type };
if (isLazyType(element) || isMemoType(element) || isForwardType(element) || isContextType(element)) {
return getProxyByType(type) || type;
}
}

return undefined;
}

export function resolveComponent(type, options = {}) {
const existingProxy = getProxyByType(type);

// cold API
Expand All @@ -35,10 +40,33 @@ export function resolveType(type, options = {}) {

if (!existingProxy && configuration.onComponentCreate) {
configuration.onComponentCreate(type, getComponentDisplayName(type));
if (shouldNotPatchComponent(type)) return type;
if (shouldNotPatchComponent(type)) {
return type;
}
}

const proxy = internalConfiguration.disableProxyCreation ? existingProxy : createProxyForType(type, options);

return proxy ? proxy.get() : type;
return proxy ? proxy.get() : undefined;
}

export function resolveProxy(type) {
if (isProxyType(type)) {
return type;
}

return undefined;
}

export function resolveNotComponent(type) {
if (!isCompositeComponent(type)) {
return type;
}

return undefined;
}

export const resolveSimpleType = type => resolveProxy(type) || resolveUtility(type) || type;

export const resolveType = (type, options = {}) =>
resolveProxy(type) || resolveUtility(type) || resolveNotComponent(type) || resolveComponent(type, options) || type;
Loading

0 comments on commit 9e666ae

Please sign in to comment.