Skip to content

Commit 3c792f3

Browse files
committed
Change implementation: permanent option
1 parent 1d6a949 commit 3c792f3

File tree

6 files changed

+76
-35
lines changed

6 files changed

+76
-35
lines changed

src/React/CHANGELOG.md

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,8 @@
22

33
## 2.21.0
44

5-
- Add Turbo `data-turbo-permanent` support. Unmounting the React component now uses
6-
a delay and can be prevented if the Stimulus controller is `deconnected` and
7-
immediately `connected` again.
5+
- Add `permanent` option to the `react_component` Twig function, to prevent the
6+
_unmounting_ when the component is deconnected and immediately re-connected.
87

98
## 2.13.2
109

src/React/assets/dist/render_controller.d.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,17 @@ import { Controller } from '@hotwired/stimulus';
33
export default class extends Controller {
44
readonly componentValue?: string;
55
readonly propsValue?: object;
6+
readonly permanentValue: boolean;
67
static values: {
78
component: StringConstructor;
89
props: ObjectConstructor;
10+
permanent: {
11+
type: BooleanConstructor;
12+
default: boolean;
13+
};
914
};
10-
private unmountTimeoutId;
1115
connect(): void;
1216
disconnect(): void;
1317
_renderReactElement(reactElement: ReactElement): void;
14-
_unmountReactElement(): void;
1518
private dispatchEvent;
1619
}

src/React/assets/dist/render_controller.js

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,6 @@ var clientExports = requireClient();
4040

4141
class default_1 extends Controller {
4242
connect() {
43-
clearTimeout(this.unmountTimeoutId);
4443
const props = this.propsValue ? this.propsValue : null;
4544
this.dispatchEvent('connect', { component: this.componentValue, props: props });
4645
if (!this.componentValue) {
@@ -55,8 +54,14 @@ class default_1 extends Controller {
5554
});
5655
}
5756
disconnect() {
58-
clearTimeout(this.unmountTimeoutId);
59-
this.unmountTimeoutId = setTimeout(this._unmountReactElement.bind(this), 50);
57+
if (this.permanentValue) {
58+
return;
59+
}
60+
this.element.root.unmount();
61+
this.dispatchEvent('unmount', {
62+
component: this.componentValue,
63+
props: this.propsValue ? this.propsValue : null,
64+
});
6065
}
6166
_renderReactElement(reactElement) {
6267
const element = this.element;
@@ -65,20 +70,14 @@ class default_1 extends Controller {
6570
}
6671
element.root.render(reactElement);
6772
}
68-
_unmountReactElement() {
69-
this.element.root.unmount();
70-
this.dispatchEvent('unmount', {
71-
component: this.componentValue,
72-
props: this.propsValue ? this.propsValue : null,
73-
});
74-
}
7573
dispatchEvent(name, payload) {
7674
this.dispatch(name, { detail: payload, prefix: 'react' });
7775
}
7876
}
7977
default_1.values = {
8078
component: String,
8179
props: Object,
80+
permanent: { type: Boolean, default: false },
8281
};
8382

8483
export { default_1 as default };

src/React/assets/src/render_controller.ts

Lines changed: 13 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -14,20 +14,17 @@ import { Controller } from '@hotwired/stimulus';
1414
export default class extends Controller {
1515
declare readonly componentValue?: string;
1616
declare readonly propsValue?: object;
17+
declare readonly permanentValue: boolean;
1718

1819
static values = {
1920
component: String,
2021
props: Object,
22+
permanent: { type: Boolean, default: false },
2123
};
22-
private unmountTimeoutId: any;
2324

2425
connect() {
25-
clearTimeout(this.unmountTimeoutId);
26-
2726
const props = this.propsValue ? this.propsValue : null;
28-
2927
this.dispatchEvent('connect', { component: this.componentValue, props: props });
30-
3128
if (!this.componentValue) {
3229
throw new Error('No component specified.');
3330
}
@@ -43,8 +40,17 @@ export default class extends Controller {
4340
}
4441

4542
disconnect() {
46-
clearTimeout(this.unmountTimeoutId);
47-
this.unmountTimeoutId = setTimeout(this._unmountReactElement.bind(this), 50);
43+
if (this.permanentValue) {
44+
// Prevent unmounting the component if the controller is permanent
45+
// (no render is allowed after unmounting)
46+
return;
47+
}
48+
49+
(this.element as any).root.unmount();
50+
this.dispatchEvent('unmount', {
51+
component: this.componentValue,
52+
props: this.propsValue ? this.propsValue : null,
53+
});
4854
}
4955

5056
_renderReactElement(reactElement: ReactElement) {
@@ -58,14 +64,6 @@ export default class extends Controller {
5864
element.root.render(reactElement);
5965
}
6066

61-
_unmountReactElement() {
62-
(this.element as any).root.unmount();
63-
this.dispatchEvent('unmount', {
64-
component: this.componentValue,
65-
props: this.propsValue ? this.propsValue : null,
66-
});
67-
}
68-
6967
private dispatchEvent(name: string, payload: any) {
7068
this.dispatch(name, { detail: payload, prefix: 'react' });
7169
}

src/React/src/Twig/ReactComponentExtension.php

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -45,15 +45,24 @@ public function getFunctions(): array
4545
];
4646
}
4747

48-
public function renderReactComponent(string $componentName, array $props = []): string
48+
/**
49+
* @param array<string, mixed> $props
50+
* @param array{permanent?: bool} $options
51+
*/
52+
public function renderReactComponent(string $componentName, array $props = [], array $options = []): string
4953
{
50-
$params = ['component' => $componentName];
54+
$values = ['component' => $componentName];
5155
if ($props) {
52-
$params['props'] = $props;
56+
$values['props'] = $props;
57+
}
58+
if ($options) {
59+
if (\is_bool($permanent = $options['permanent'] ?? null)) {
60+
$values['permanent'] = $permanent;
61+
}
5362
}
5463

5564
$stimulusAttributes = $this->stimulusHelper->createStimulusAttributes();
56-
$stimulusAttributes->addController('@symfony/ux-react/react', $params);
65+
$stimulusAttributes->addController('@symfony/ux-react/react', $values);
5766

5867
return (string) $stimulusAttributes;
5968
}

src/React/tests/Twig/ReactComponentExtensionTest.php

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
* file that was distributed with this source code.
1010
*/
1111

12-
namespace Symfony\UX\React\Tests;
12+
namespace Symfony\UX\React\Tests\Twig;
1313

1414
use PHPUnit\Framework\TestCase;
1515
use Symfony\UX\React\Tests\Kernel\TwigAppKernel;
@@ -41,6 +41,39 @@ public function testRenderComponent()
4141
);
4242
}
4343

44+
/**
45+
* @dataProvider provideOptions
46+
*/
47+
public function testRenderComponentWithOptions(array $options, string|false $expected)
48+
{
49+
$kernel = new TwigAppKernel('test', true);
50+
$kernel->boot();
51+
52+
/** @var ReactComponentExtension $extension */
53+
$extension = $kernel->getContainer()->get('test.twig.extension.react');
54+
55+
$rendered = $extension->renderReactComponent(
56+
'SubDir/MyComponent',
57+
['fullName' => 'Titouan Galopin'],
58+
$options,
59+
);
60+
61+
$this->assertStringContainsString('data-controller="symfony--ux-react--react" data-symfony--ux-react--react-component-value="SubDir/MyComponent" data-symfony--ux-react--react-props-value="{&quot;fullName&quot;:&quot;Titouan Galopin&quot;}"', $rendered);
62+
if (false === $expected) {
63+
$this->assertStringNotContainsString('data-symfony--ux-react--react-permanent-value', $rendered);
64+
} else {
65+
$this->assertStringContainsString($expected, $rendered);
66+
}
67+
}
68+
69+
public static function provideOptions(): iterable
70+
{
71+
yield 'permanent' => [['permanent' => true], 'data-symfony--ux-react--react-permanent-value="true"'];
72+
yield 'not permanent' => [['permanent' => false], 'data-symfony--ux-react--react-permanent-value="false"'];
73+
yield 'permanent not bool' => [['permanent' => 12345], false];
74+
yield 'no permanent' => [[], false];
75+
}
76+
4477
public function testRenderComponentWithoutProps()
4578
{
4679
$kernel = new TwigAppKernel('test', true);

0 commit comments

Comments
 (0)