Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 8 additions & 8 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,14 @@
"author": "Elie Rotenberg <elie@rotenberg.io>",
"license": "MIT",
"devDependencies": {
"@testing-library/react": "^14.1.2",
"@testing-library/dom": "^10.4.2",
"@testing-library/react": "^16.3.3",
"@types/assert": "^1.5.10",
"@types/mocha": "^10.0.6",
"@types/node": "^20.10.0",
"@types/react": "^18.2.39",
"@types/react-dom": "^18.2.17",
"@types/react": "^19.0.0",
"@types/react-dom": "^19.0.0",
"@types/sinon": "^22.0.0",
"@typescript-eslint/eslint-plugin": "^6.13.1",
"@typescript-eslint/parser": "^6.13.1",
"@vitejs/plugin-react": "^4.2.0",
Expand All @@ -49,17 +51,15 @@
"koa": "^2.14.2",
"node-fetch": "^3.3.2",
"prettier": "^3.1.0",
"prop-types": "^15.8.1",
"react": "^18.0.0",
"react-dom": "^18.0.0",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"rimraf": "^5.0.5",
"sinon": "^17.0.1",
"typescript": "^5.3.2",
"vite": "^5.0.2",
"vitest": "^0.34.6"
},
"peerDependencies": {
"prop-types": "^15.7.2",
"react": "^16.8.2 | ^17.0.0 | ^18.0.0"
"react": "^19.0.0"
}
}
21 changes: 9 additions & 12 deletions src/prepare.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,32 +112,29 @@ async function prepareElement(
}
case ELEMENT_TYPE.DOM_ELEMENT:
case ELEMENT_TYPE.FRAGMENT: {
return [(element as ReactElement).props.children, context];
return [
(element as ReactElement<{ children?: ReactNode }>).props.children,
context,
];
}
case ELEMENT_TYPE.CONTEXT_PROVIDER: {
const providerElement = element as ProviderElement;
const _providers: ContextProviderMap = new Map(context._providers);
_providers.set(
providerElement.type._context.Provider,
providerElement.props,
);
_providers.set(providerElement.type.Provider, providerElement.props);
return [providerElement.props.children, { _providers }];
}
case ELEMENT_TYPE.CONTEXT_CONSUMER: {
const consumerElement = element as ConsumerElement;
const value = getContextValue(context, consumerElement.type);
const value = getContextValue(context, consumerElement.type._context);

const consumerFunc = consumerElement.props.children;
return [consumerFunc(value), context];
}
case ELEMENT_TYPE.FORWARD_REF: {
const forwardRefElement = element as ForwardRefElement;
const { ref, ...props } = forwardRefElement.props;
const children = await runFunctionComponent(
() =>
forwardRefElement.type.render(
forwardRefElement.props,
forwardRefElement.ref,
),
() => forwardRefElement.type.render(props, ref),
dispatcher,
context,
);
Expand All @@ -155,7 +152,7 @@ async function prepareElement(
case ELEMENT_TYPE.FUNCTION_COMPONENT: {
const functionElement = element as FunctionComponentElement<unknown>;
const children = await runFunctionComponent(
() => functionElement.type(functionElement.props),
() => functionElement.type(functionElement.props) as ReactNode,
dispatcher,
context,
);
Expand Down
15 changes: 6 additions & 9 deletions src/tests/ReactLifeCycle.spec.jsx
Original file line number Diff line number Diff line change
@@ -1,28 +1,25 @@
import React from 'react';
import { describe, it } from 'vitest';
import assert from 'assert/strict';
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { Component } from 'react';
import { renderToString } from 'react-dom/server';
import sinon from 'sinon';

describe('React lifecycle methods', () => {
class CompositeComponent extends Component {
static propTypes = {
spyForComponentWillMount: PropTypes.func,
spyForComponentWillUnmount: PropTypes.func,
};

// eslint-disable-next-line react/no-deprecated
componentWillMount() {
const { spyForComponentWillMount } = this.props;
spyForComponentWillMount();
spyForComponentWillMount && spyForComponentWillMount();
}

// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
componentWillUnmount() {
const { spyForComponentWillUnmount } = this.props;
spyForComponentWillUnmount();
spyForComponentWillUnmount && spyForComponentWillUnmount();
}

// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
render() {
return <div>CompositeComponent</div>;
}
Expand Down
2 changes: 0 additions & 2 deletions src/tests/getElementType.spec.jsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
/* eslint-disable react/prop-types */

import { describe, it } from 'vitest';
import React, { createContext, memo } from 'react';
import assert from 'assert/strict';
Expand Down
28 changes: 4 additions & 24 deletions src/tests/prepare.spec.jsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/* eslint-disable react/prop-types */
/* eslint-disable react/display-name */

import { describe, it, beforeAll, beforeEach, expect } from 'vitest';
import assert from 'assert/strict';
Expand All @@ -21,7 +21,6 @@ import React, {
useSyncExternalStore,
useTransition,
} from 'react';
import PropTypes from 'prop-types';
import { renderToStaticMarkup } from 'react-dom/server';
import prepare from '../prepare';
import { usePreparedEffect, withPreparedEffect } from '../index';
Expand All @@ -30,20 +29,15 @@ describe('prepare', () => {
let originalDispatcher;
beforeAll(() => {
originalDispatcher =
React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED
.ReactCurrentDispatcher.current;
React.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE.H;
});
beforeEach(() => {
React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.ReactCurrentDispatcher.current =
React.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE.H =
originalDispatcher;
});

it('sets instance properties', async () => {
class MessageBox extends React.Component {
static propTypes = {
message: PropTypes.string,
};

constructor() {
super();
}
Expand Down Expand Up @@ -295,8 +289,6 @@ describe('prepare', () => {
));

const Testing = ({ children }) => <div>Test {children} </div>;
Testing.propTypes = { children: PropTypes.node };

await prepare(
<App text="foo">
<App text="foo" />
Expand Down Expand Up @@ -562,19 +554,11 @@ describe('prepare', () => {
</p>
);
};
MyContextConsumer.propTypes = {
expectedData: PropTypes.string,
children: PropTypes.node,
};
const AnotherContextConsumer = (props) => {
const data = useContext(AnotherContext);
assert.equal(data, props.expectedData);
return <p>Another:{data}</p>;
};
AnotherContextConsumer.propTypes = {
expectedData: PropTypes.string,
};

const App = () => (
<>
<MyContextConsumer expectedData="initial" />
Expand Down Expand Up @@ -799,11 +783,7 @@ describe('prepare', () => {
</li>
</ul>
);
App.propTypes = {
texts: PropTypes.array,
};

await prepare(<App texts={['first', 'second']} />);
await prepare(<App texts={['first', 'second']} />);

assert(
prepareUsingPropsForFirstChild.calledOnce,
Expand Down
1 change: 0 additions & 1 deletion src/tests/usePreparedEffect.spec.jsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
/* eslint-disable react/prop-types */
import { describe, it, beforeEach } from 'vitest';
import React from 'react';
import assert from 'assert/strict';
Expand Down
11 changes: 0 additions & 11 deletions src/tests/withPreparedEffect.spec.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,36 +2,25 @@ import { describe, it, beforeEach } from 'vitest';
import sinon from 'sinon';
import assert from 'assert/strict';
import React, { Component, PureComponent } from 'react';
import PropTypes from 'prop-types';
import { renderToStaticMarkup } from 'react-dom/server';

import { prepare, withPreparedEffect } from '../index';
import { render } from '@testing-library/react';

describe('withPreparedEffect', () => {
class OriginalCompositeComponent extends Component {
static propTypes = {
text: PropTypes.string,
};
render() {
return <div>{this.props.text}</div>;
}
}

class OriginalCompositePureComponent extends PureComponent {
static propTypes = {
text: PropTypes.string,
};
render() {
return <div>{this.props.text}</div>;
}
}

const OriginalArrowComponent = ({ text }) => <div>{text}</div>;
OriginalArrowComponent.propTypes = {
text: PropTypes.string,
};

let doAsyncSideEffect;
let prepareUsingProps;

Expand Down
1 change: 1 addition & 0 deletions src/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ export type ClassComponentInstance<P, S = unknown> = Omit<
'props'
> & {
props: P;
refs?: Record<string, unknown>;
updater?: {
enqueueSetState: (
publicInstance: Component<P, S>,
Expand Down
4 changes: 2 additions & 2 deletions src/utils/dispatcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export type Dispatcher = ReactDispatcher & {
};

const ReactInternals = (React as ReactWithInternals)
.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED;
.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE;

const noOp = (): void => {
// do nothing
Expand Down Expand Up @@ -112,7 +112,7 @@ export const setDispatcherContext = (
};

export const registerDispatcher = (dispatcher: Dispatcher): void => {
ReactInternals.ReactCurrentDispatcher.current = dispatcher;
ReactInternals.H = dispatcher;
};

export const popHookPromises = (dispatcher: Dispatcher): Promise<unknown>[] => {
Expand Down
8 changes: 3 additions & 5 deletions src/utils/getContextValue.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,13 @@ import { ContextWithInternals } from './reactInternalTypes';

const getContextValue = <T>(
prepareContext: PrepareContext,
elementContext: Context<T>,
context: Context<T>,
): T => {
const parentProvider = prepareContext._providers?.get(
elementContext.Provider,
);
const parentProvider = prepareContext._providers?.get(context.Provider);

return parentProvider
? parentProvider.value
: (elementContext as ContextWithInternals<T>)._currentValue;
: (context as ContextWithInternals<T>)._currentValue;
};

export default getContextValue;
4 changes: 2 additions & 2 deletions src/utils/getElementType.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,9 @@ export default function getElementType(element: ReactNode): ELEMENT_TYPE {
} else if (typeof element.type === 'object') {
// Exotic components
const type: ExoticComponent = element.type;
if (type.$$typeof.toString() === 'Symbol(react.provider)') {
if (type.$$typeof.toString() === 'Symbol(react.context)') {
return ELEMENT_TYPE.CONTEXT_PROVIDER;
} else if (type.$$typeof.toString() === 'Symbol(react.context)') {
} else if (type.$$typeof.toString() === 'Symbol(react.consumer)') {
return ELEMENT_TYPE.CONTEXT_CONSUMER;
} else if (type.$$typeof.toString() === 'Symbol(react.forward_ref)') {
return ELEMENT_TYPE.FORWARD_REF;
Expand Down
21 changes: 8 additions & 13 deletions src/utils/reactInternalTypes.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import React, {
ForwardRefRenderFunction,
LazyExoticComponent,
MemoExoticComponent,
Provider,
ProviderProps,
ReactElement,
useCallback,
Expand Down Expand Up @@ -48,41 +47,37 @@ export interface ReactDispatcher {
}

export type ReactWithInternals = typeof React & {
__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED: {
ReactCurrentDispatcher: {
current: ReactDispatcher;
};
__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE: {
H: ReactDispatcher | null;
};
};

export interface ContextWithInternals<T> extends Context<T> {
_currentValue: T;
}

interface ProviderWithInternals<T = unknown> extends Provider<T> {
_context: Context<T>;
export interface ConsumerWithInternals<T> extends Consumer<T> {
_context: ContextWithInternals<T>;
}

export type ProviderElement = ReactElement<
ProviderProps<unknown>,
ProviderWithInternals
ContextWithInternals<unknown>
>;

export type ConsumerElement = ReactElement<
ConsumerProps<unknown>,
Context<unknown> & Consumer<unknown>
ConsumerWithInternals<unknown>
>;

// I couldn't find any exported types in react that matched.
// ForwardRefExoticComponent seems completely wrong.
export type ForwardRefElement<P = unknown> = ReactElement<
P,
P & { ref: ForwardedRef<unknown> },
ExoticComponent<P> & {
render: ForwardRefRenderFunction<unknown, P>;
}
> & {
ref: ForwardedRef<unknown>;
};
>;

export type MemoElement<P = unknown> = ReactElement<
P,
Expand Down
Loading
Loading