Skip to content

Commit 7ad294f

Browse files
committed
Allow to use react_component without props
1 parent f1711ec commit 7ad294f

File tree

4 files changed

+62
-22
lines changed

4 files changed

+62
-22
lines changed

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)