Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import React from 'react';
import ReactDOM from 'react-dom';
import { wrapElementInStrictMode } from '../../app/strictModeSupport';
Comment thread
justin808 marked this conversation as resolved.

export default (props, _railsContext, domNodeId) => {
const reactElement = (
const reactElement = wrapElementInStrictMode(
<div>
<h1 id="manual-render">Manual Render Example</h1>
<p>If you can see this, you can register renderer functions.</p>
</div>
</div>,
);

const domNode = document.getElementById(domNodeId);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import reducers from '../../app/reducers/reducersIndex';
import composeInitialState from '../../app/store/composeInitialState';

import HelloWorldContainer from '../../app/components/HelloWorldContainer';
import { wrapElementInStrictMode } from '../../app/strictModeSupport';

/*
* Export a function that takes the props and returns a ReactComponent.
Expand All @@ -37,10 +38,10 @@ export default (props, railsContext, domNodeId) => {
// Provider uses this.props.children, so we're not typical React syntax.
// This allows redux to add additional props to the HelloWorldContainer.
const renderApp = (Komponent) => {
const element = (
const element = wrapElementInStrictMode(
<Provider store={store}>
<Komponent />
</Provider>
</Provider>,
);

render(element, document.getElementById(domNodeId));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import ReactOnRails from 'react-on-rails/client';
import ReactDOM from 'react-dom';

import HelloWorldContainer from '../../app/components/HelloWorldContainer';
import { wrapElementInStrictMode } from '../../app/strictModeSupport';

/*
* Export a function that returns a ReactComponent, depending on a store named SharedReduxStore.
Expand All @@ -27,10 +28,10 @@ export default (props, _railsContext, domNodeId) => {
// Provider uses this.props.children, so we're not typical React syntax.
// This allows redux to add additional props to the HelloWorldContainer.
const renderApp = (Component) => {
const element = (
const element = wrapElementInStrictMode(
<Provider store={store}>
<Component />
</Provider>
</Provider>,
);
render(element, document.getElementById(domNodeId));
};
Expand Down
5 changes: 5 additions & 0 deletions react_on_rails/spec/dummy/client/app/packs/client-bundle.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@ import ReactOnRails from 'react-on-rails/client';
import HelloTurboStream from '../startup/HelloTurboStream';
import ManualRenderComponent from '../startup/ManualRenderComponent';
import SharedReduxStore from '../stores/SharedReduxStore';
import { wrapRegisteredComponentsWithStrictMode } from '../strictModeSupport';

const originalRegister = ReactOnRails.register.bind(ReactOnRails);
Comment thread
justin808 marked this conversation as resolved.
Outdated

ReactOnRails.register = (components) => originalRegister(wrapRegisteredComponentsWithStrictMode(components));
Comment thread
justin808 marked this conversation as resolved.
Outdated
Comment thread
justin808 marked this conversation as resolved.
Outdated
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated

ReactOnRails.setOptions({
traceTurbolinks: true,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import React from 'react';
import ReactDOMClient from 'react-dom/client';
import { wrapElementInStrictMode } from '../strictModeSupport';

export default (props, _railsContext, domNodeId) => {
const reactElement = (
const reactElement = wrapElementInStrictMode(
<div>
<h1 id="manual-render">Manual Render Example</h1>
<p>If you can see this, you can register renderer functions.</p>
</div>
</div>,
);

const domNode = document.getElementById(domNodeId);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import reducers from '../reducers/reducersIndex';
import composeInitialState from '../store/composeInitialState';

import HelloWorldContainer from '../components/HelloWorldContainer';
import { wrapElementInStrictMode } from '../strictModeSupport';

/*
* Export a function that takes the props and returns a ReactComponent.
Expand Down Expand Up @@ -42,10 +43,10 @@ export default (props, railsContext, domNodeId) => {
// Provider uses this.props.children, so we're not typical React syntax.
// This allows redux to add additional props to the HelloWorldContainer.
const renderApp = (Komponent) => {
const element = (
const element = wrapElementInStrictMode(
<Provider store={store}>
<Komponent />
</Provider>
</Provider>,
);

render(document.getElementById(domNodeId), element);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import ReactOnRails from 'react-on-rails/client';
import ReactDOMClient from 'react-dom/client';

import HelloWorldContainer from '../components/HelloWorldContainer';
import { wrapElementInStrictMode } from '../strictModeSupport';

/*
* Export a function that returns a ReactComponent, depending on a store named SharedReduxStore.
Expand All @@ -32,10 +33,10 @@ export default (props, _railsContext, domNodeId) => {
// Provider uses this.props.children, so we're not typical React syntax.
// This allows redux to add additional props to the HelloWorldContainer.
const renderApp = (Component) => {
const element = (
const element = wrapElementInStrictMode(
<Provider store={store}>
<Component />
</Provider>
</Provider>,
);
render(document.getElementById(domNodeId), element);
};
Expand Down
70 changes: 70 additions & 0 deletions react_on_rails/spec/dummy/client/app/strictModeSupport.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import React from 'react';

const wrappedFunctionComponents = new WeakMap();
const wrappedOtherComponents = new Map();
Comment thread
justin808 marked this conversation as resolved.
Outdated
Comment thread
justin808 marked this conversation as resolved.
Outdated

const isRenderFunction = (component) => {
if (typeof component !== 'function') {
return false;
}

if (component.prototype?.isReactComponent) {
return false;
}

if (component.renderFunction) {
return true;
}

return component.length >= 2;
Comment thread
justin808 marked this conversation as resolved.
Comment thread
justin808 marked this conversation as resolved.
Comment thread
justin808 marked this conversation as resolved.
Outdated
};

const createStrictModeWrapper = (Component) => {
function StrictModeWrapper(props) {
return (
<React.StrictMode>
{typeof Component === 'function' ? <Component {...props} /> : React.createElement(Component, props)}
Comment thread
justin808 marked this conversation as resolved.
Outdated
</React.StrictMode>
Comment thread
justin808 marked this conversation as resolved.
Outdated
);
}

const componentName =
typeof Component === 'string'
? Component
: Component.displayName || Component.name || 'AnonymousComponent';
StrictModeWrapper.displayName = `StrictMode(${componentName})`;

return StrictModeWrapper;
};

const wrapComponentInStrictMode = (component) => {
if (typeof component === 'function') {
const cachedComponent = wrappedFunctionComponents.get(component);
if (cachedComponent) {
return cachedComponent;
}

const wrappedComponent = createStrictModeWrapper(component);
wrappedFunctionComponents.set(component, wrappedComponent);
return wrappedComponent;
}

const cachedComponent = wrappedOtherComponents.get(component);
if (cachedComponent) {
return cachedComponent;
}

const wrappedComponent = createStrictModeWrapper(component);
wrappedOtherComponents.set(component, wrappedComponent);
return wrappedComponent;
};

export const wrapElementInStrictMode = (reactElement) => <React.StrictMode>{reactElement}</React.StrictMode>;

export const wrapRegisteredComponentsWithStrictMode = (components) =>
Object.fromEntries(
Object.entries(components).map(([name, component]) => [
name,
isRenderFunction(component) ? component : wrapComponentInStrictMode(component),
Comment thread
justin808 marked this conversation as resolved.
Outdated
Comment thread
justin808 marked this conversation as resolved.
Outdated
]),
);
54 changes: 54 additions & 0 deletions react_on_rails/spec/dummy/tests/strict-mode-support.test.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import PropTypes from 'prop-types';
import React from 'react';

import {
wrapElementInStrictMode,
wrapRegisteredComponentsWithStrictMode,
} from '../client/app/strictModeSupport';

describe('strictModeSupport', () => {
it('wraps registered React components in StrictMode', () => {
const HelloWorld = ({ greeting }) => <div>{greeting}</div>;
HelloWorld.propTypes = {
greeting: PropTypes.string.isRequired,
};
const wrappedComponent = wrapRegisteredComponentsWithStrictMode({ HelloWorld }).HelloWorld;

expect(wrappedComponent).not.toBe(HelloWorld);

const wrappedElement = wrappedComponent({ greeting: 'hello' });
expect(wrappedElement.type).toBe(React.StrictMode);
expect(wrappedElement.props.children.type).toBe(HelloWorld);
expect(wrappedElement.props.children.props.greeting).toBe('hello');
});

it('reuses the same wrapper for repeated registrations of the same component', () => {
const HelloWorld = () => <div>Hello</div>;

const firstWrappedComponent = wrapRegisteredComponentsWithStrictMode({ HelloWorld }).HelloWorld;
const secondWrappedComponent = wrapRegisteredComponentsWithStrictMode({ HelloWorld }).HelloWorld;

expect(firstWrappedComponent).toBe(secondWrappedComponent);
});

it('does not wrap render functions or renderer functions', () => {
Comment thread
justin808 marked this conversation as resolved.
Outdated
const renderFunction = (props, railsContext) => ({ props, railsContext });
const rendererFunction = (props, railsContext, domNodeId) => ({ props, railsContext, domNodeId });

const wrappedComponents = wrapRegisteredComponentsWithStrictMode({
renderFunction,
rendererFunction,
});

expect(wrappedComponents.renderFunction).toBe(renderFunction);
expect(wrappedComponents.rendererFunction).toBe(rendererFunction);
});
Comment thread
justin808 marked this conversation as resolved.
Outdated

it('wraps manual render trees in StrictMode', () => {
const innerElement = <div>hello</div>;
const wrappedElement = wrapElementInStrictMode(innerElement);

expect(wrappedElement.type).toBe(React.StrictMode);
expect(wrappedElement.props.children).toBe(innerElement);
});
});
Comment thread
justin808 marked this conversation as resolved.
Loading