Skip to content

Commit ca84948

Browse files
tgalopinweaverryan
authored andcommitted
Allow to use react_component without props
1 parent 990fb8e commit ca84948

File tree

6 files changed

+98
-48
lines changed

6 files changed

+98
-48
lines changed

src/Autocomplete/assets/dist/controller.js

Lines changed: 22 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,25 @@
11
import { Controller } from '@hotwired/stimulus';
22
import TomSelect from 'tom-select';
33

4-
/*! *****************************************************************************
5-
Copyright (c) Microsoft Corporation.
6-
7-
Permission to use, copy, modify, and/or distribute this software for any
8-
purpose with or without fee is hereby granted.
9-
10-
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
11-
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
12-
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
13-
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
14-
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
15-
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
16-
PERFORMANCE OF THIS SOFTWARE.
17-
***************************************************************************** */
18-
19-
function __classPrivateFieldGet(receiver, state, kind, f) {
20-
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
21-
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
22-
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
4+
/******************************************************************************
5+
Copyright (c) Microsoft Corporation.
6+
7+
Permission to use, copy, modify, and/or distribute this software for any
8+
purpose with or without fee is hereby granted.
9+
10+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
11+
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
12+
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
13+
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
14+
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
15+
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
16+
PERFORMANCE OF THIS SOFTWARE.
17+
***************************************************************************** */
18+
19+
function __classPrivateFieldGet(receiver, state, kind, f) {
20+
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
21+
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
22+
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
2323
}
2424

2525
var _instances, _getCommonConfig, _createAutocomplete, _createAutocompleteWithHtmlContents, _createAutocompleteWithRemoteData, _stripTags, _mergeObjects, _createTomSelect, _dispatchEvent;
@@ -42,6 +42,9 @@ class default_1 extends Controller {
4242
}
4343
this.tomSelect = __classPrivateFieldGet(this, _instances, "m", _createAutocomplete).call(this);
4444
}
45+
disconnect() {
46+
this.tomSelect.destroy();
47+
}
4548
get selectElement() {
4649
if (!(this.element instanceof HTMLSelectElement)) {
4750
return null;

src/React/Resources/assets/dist/render_controller.js

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ var createRoot;
77
var m = require$$0;
88
if (process.env.NODE_ENV === 'production') {
99
createRoot = m.createRoot;
10+
m.hydrateRoot;
1011
} else {
1112
var i = m.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED;
1213
createRoot = function(c, o) {
@@ -21,23 +22,29 @@ if (process.env.NODE_ENV === 'production') {
2122

2223
class default_1 extends Controller {
2324
connect() {
24-
this._dispatchEvent('react:connect', { component: this.componentValue, props: this.propsValue });
25+
const props = this.propsValue ? this.propsValue : null;
26+
this._dispatchEvent('react:connect', { component: this.componentValue, props: props });
2527
const component = window.resolveReactComponent(this.componentValue);
26-
this._renderReactElement(React.createElement(component, this.propsValue, null));
28+
this._renderReactElement(React.createElement(component, props, null));
2729
this._dispatchEvent('react:mount', {
2830
componentName: this.componentValue,
2931
component: component,
30-
props: this.propsValue,
32+
props: props,
3133
});
3234
}
3335
disconnect() {
3436
this.element.root.unmount();
35-
this._dispatchEvent('react:unmount', { component: this.componentValue, props: this.propsValue });
37+
this._dispatchEvent('react:unmount', {
38+
component: this.componentValue,
39+
props: this.propsValue ? this.propsValue : null,
40+
});
3641
}
3742
_renderReactElement(reactElement) {
38-
const root = createRoot(this.element);
39-
root.render(reactElement);
40-
this.element.root = root;
43+
const element = this.element;
44+
if (!element.root) {
45+
element.root = createRoot(this.element);
46+
}
47+
element.root.render(reactElement);
4148
}
4249
_dispatchEvent(name, payload) {
4350
this.element.dispatchEvent(new CustomEvent(name, { detail: payload, bubbles: true }));

src/React/Resources/assets/src/render_controller.ts

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,36 +15,45 @@ import { Controller } from '@hotwired/stimulus';
1515

1616
export default class extends Controller {
1717
readonly componentValue: string;
18-
readonly propsValue: object;
18+
readonly propsValue?: object;
1919

2020
static values = {
2121
component: String,
2222
props: Object,
2323
};
2424

2525
connect() {
26-
this._dispatchEvent('react:connect', { component: this.componentValue, props: this.propsValue });
26+
const props = this.propsValue ? this.propsValue : null;
27+
28+
this._dispatchEvent('react:connect', { component: this.componentValue, props: props });
2729

2830
const component = window.resolveReactComponent(this.componentValue);
29-
this._renderReactElement(React.createElement(component, this.propsValue, null));
31+
this._renderReactElement(React.createElement(component, props, null));
3032

3133
this._dispatchEvent('react:mount', {
3234
componentName: this.componentValue,
3335
component: component,
34-
props: this.propsValue,
36+
props: props,
3537
});
3638
}
3739

3840
disconnect() {
3941
(this.element as any).root.unmount();
40-
this._dispatchEvent('react:unmount', { component: this.componentValue, props: this.propsValue });
42+
this._dispatchEvent('react:unmount', {
43+
component: this.componentValue,
44+
props: this.propsValue ? this.propsValue : null,
45+
});
4146
}
4247

4348
_renderReactElement(reactElement: ReactElement) {
44-
const root = createRoot(this.element);
45-
root.render(reactElement);
49+
const element: any = this.element as any;
50+
51+
// If a root has already been created for this element, reuse it
52+
if (!element.root) {
53+
element.root = createRoot(this.element);
54+
}
4655

47-
(this.element as any).root = root;
56+
element.root.render(reactElement);
4857
}
4958

5059
_dispatchEvent(name: string, payload: any) {

src/React/Resources/assets/test/render_controller.test.tsx

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -34,38 +34,51 @@ const startStimulus = () => {
3434
application.register('react', ReactController);
3535
};
3636

37-
function ReactComponent(props: { fullName: string }) {
38-
return <div>Hello {props.fullName}</div>;
37+
function ReactComponent(props: { fullName?: string }) {
38+
return <div>Hello {props.fullName ? props.fullName : 'without props'}</div>;
3939
}
4040

4141
(window as any).resolveReactComponent = () => {
4242
return ReactComponent;
4343
};
4444

4545
describe('ReactController', () => {
46-
let container: any;
47-
48-
beforeEach(() => {
49-
container = mountDOM(`
46+
it('connect with props', async () => {
47+
const container = mountDOM(`
5048
<div data-testid="component"
5149
data-controller="check react"
5250
data-react-component-value="ReactComponent"
5351
data-react-props-value="{&quot;fullName&quot;: &quot;Titouan Galopin&quot;}" />
5452
`);
55-
});
5653

57-
afterEach(() => {
54+
const component = getByTestId(container, 'component');
55+
expect(component).not.toHaveClass('connected');
56+
expect(component).not.toHaveClass('mounted');
57+
58+
startStimulus();
59+
await waitFor(() => expect(component).toHaveClass('connected'));
60+
await waitFor(() => expect(component).toHaveClass('mounted'));
61+
await waitFor(() => expect(component.innerHTML).toEqual('<div>Hello Titouan Galopin</div>'));
62+
5863
clearDOM();
5964
});
6065

61-
it('connect', async () => {
66+
it('connect without props', async () => {
67+
const container = mountDOM(`
68+
<div data-testid="component" id="container-2"
69+
data-controller="check react"
70+
data-react-component-value="ReactComponent" />
71+
`);
72+
6273
const component = getByTestId(container, 'component');
6374
expect(component).not.toHaveClass('connected');
6475
expect(component).not.toHaveClass('mounted');
6576

6677
startStimulus();
6778
await waitFor(() => expect(component).toHaveClass('connected'));
6879
await waitFor(() => expect(component).toHaveClass('mounted'));
69-
await waitFor(() => expect(component.innerHTML).toEqual('<div>Hello Titouan Galopin</div>'));
80+
await waitFor(() => expect(component.innerHTML).toEqual('<div>Hello without props</div>'));
81+
82+
clearDOM();
7083
});
7184
});

src/React/Tests/Twig/ReactComponentExtensionTest.php

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,4 +41,20 @@ public function testRenderComponent()
4141
$rendered
4242
);
4343
}
44+
45+
public function testRenderComponentWithoutProps()
46+
{
47+
$kernel = new TwigAppKernel('test', true);
48+
$kernel->boot();
49+
50+
/** @var ReactComponentExtension $extension */
51+
$extension = $kernel->getContainer()->get('test.twig.extension.react');
52+
53+
$rendered = $extension->renderReactComponent($kernel->getContainer()->get('test.twig'), 'SubDir/MyComponent');
54+
55+
$this->assertSame(
56+
'data-controller="symfony--ux-react--react" data-symfony--ux-react--react-component-value="SubDir&#x2F;MyComponent"',
57+
$rendered
58+
);
59+
}
4460
}

src/React/Twig/ReactComponentExtension.php

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -40,9 +40,11 @@ public function getFunctions(): array
4040

4141
public function renderReactComponent(Environment $env, string $componentName, array $props = []): string
4242
{
43-
return $this->stimulusExtension->renderStimulusController($env, '@symfony/ux-react/react', [
44-
'component' => $componentName,
45-
'props' => $props,
46-
]);
43+
$params = ['component' => $componentName];
44+
if ($props) {
45+
$params['props'] = $props;
46+
}
47+
48+
return $this->stimulusExtension->renderStimulusController($env, '@symfony/ux-react/react', $params);
4749
}
4850
}

0 commit comments

Comments
 (0)