Skip to content

Commit

Permalink
Fix reviews
Browse files Browse the repository at this point in the history
  • Loading branch information
tgalopin committed Jun 4, 2022
1 parent c52e713 commit 4059895
Show file tree
Hide file tree
Showing 7 changed files with 118 additions and 106 deletions.
2 changes: 1 addition & 1 deletion src/React/DependencyInjection/ReactExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
use Symfony\UX\React\Twig\ReactComponentExtension;

/**
* @author Titouan Galopin <galopintitouan@gm ail.com>
* @author Titouan Galopin <galopintitouan@gmail.com>
*
* @internal
*/
Expand Down
51 changes: 2 additions & 49 deletions src/React/README.md
Original file line number Diff line number Diff line change
@@ -1,58 +1,11 @@
# Symfony UX React

Symfony UX React integrates [React](https://reactjs.org/) into Symfony applications. It provides tools
to render React components from Twig.
Symfony UX React integrates [React](https://reactjs.org/) into Symfony applications.
It provides tools to render React components from Twig.

**This repository is a READ-ONLY sub-tree split**. See
https://github.com/symfony/ux to create issues or submit pull requests.

## Usage

```bash
composer require symfony/ux-react

# Don't forget to install the JavaScript dependencies as well and compile
$ yarn install --force
$ yarn encore dev
```

Then in your `assets/app.js` file, add the following lines:

```
// assets/app.js
import { registerReactControllerComponents } from '@symfony/ux-react';
// Registers React controller components to allow loading them from Twig
//
// React controller components are components that are meant to be rendered
// from Twig. These component then rely on other components that won't be called
// directly from Twig.
//
// By putting only controller components in `react/controllers`, you ensure that
// internal components won't be automatically included in your JS built file if
// they are not necessary.
registerReactControllerComponents(require.context('./react/controllers', true, /\.(j|t)sx?$/));
```

You can now create your first React controller component in `assets/react/controllers`:

```js
// assets/react/controllers/MyComponent.jsx
import React from 'react';

export default function (props) {
return <div>Hello {props.fullName}</div>;
}
```

And use it in your Twig files:

```twig
{# templates/home.html.twig #}
<div {{ react_component('MyComponent', { 'fullName': app.user.fullName }) }}></div>
```

## Resources

- [Documentation](https://symfony.com/bundles/ux-react/current/index.html)
Expand Down
17 changes: 8 additions & 9 deletions src/React/Resources/assets/dist/render_controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,13 @@ import { Controller } from '@hotwired/stimulus';
class default_1 extends Controller {
connect() {
this._dispatchEvent('react:connect', { component: this.componentValue, props: this.propsValue });
if (this.element.timeout) {
clearTimeout(this.element.timeout);
}
this.element.timeout = setTimeout(() => {
const component = window.resolveReactComponent(this.componentValue);
this._renderReactElement(React.createElement(component, this.propsValue, null));
this._dispatchEvent('react:mount', { component: this.componentValue, props: this.propsValue });
}, 50);
const component = window.resolveReactComponent(this.componentValue);
this._renderReactElement(React.createElement(component, this.propsValue, null));
this._dispatchEvent('react:mount', {
componentName: this.componentValue,
component: component,
props: this.propsValue,
});
}
disconnect() {
this.element.unmount();
Expand All @@ -33,7 +32,7 @@ class default_1 extends Controller {
};
}
_dispatchEvent(name, payload) {
this.element.dispatchEvent(new CustomEvent(name, { detail: payload }));
this.element.dispatchEvent(new CustomEvent(name, { detail: payload, bubbles: true }));
}
}
default_1.values = {
Expand Down
15 changes: 7 additions & 8 deletions src/React/Resources/assets/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,15 @@
},
"peerDependencies": {
"@hotwired/stimulus": "^3.0.0",
"react": "^16.0 || ^17.0 || ^18.0",
"react-dom": "^16.0 || ^17.0 || ^18.0"
},
"dependencies": {
"@types/webpack-env": "^1.16"
"react": "^18.0",
"react-dom": "^18.0"
},
"devDependencies": {
"@hotwired/stimulus": "^3.0.0",
"@types/react": "^16.0 || ^17.0 || ^18.0",
"react": "^16.0 || ^17.0 || ^18.0",
"react-dom": "^16.0 || ^17.0 || ^18.0"
"@types/react": "^18.0",
"@types/react-dom": "^18.0",
"@types/webpack-env": "^1.16",
"react": "^18.0",
"react-dom": "^18.0"
}
}
2 changes: 1 addition & 1 deletion src/React/Resources/assets/src/register_controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export function registerReactControllerComponents(context: __WebpackModuleApi.Re

// Expose a global React loader to allow rendering from the Stimulus controller
(window as any).resolveReactComponent = (name: string): object => {
const component = reactControllers['./' + name + '.jsx'] || reactControllers['./' + name + '.tsx'];
const component = reactControllers[`./${name}.jsx`] || reactControllers[`./${name}.tsx`];
if (typeof component === 'undefined') {
throw new Error('React controller "' + name + '" does not exist');
}
Expand Down
49 changes: 12 additions & 37 deletions src/React/Resources/assets/src/render_controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
'use strict';

import React, { ReactElement } from 'react';
import { createRoot } from 'react-dom/client';
import { Controller } from '@hotwired/stimulus';

export default class extends Controller {
Expand All @@ -24,52 +25,26 @@ export default class extends Controller {
connect() {
this._dispatchEvent('react:connect', { component: this.componentValue, props: this.propsValue });

// Use a timeout to avoid mounting and demounting right after
if ((this.element as any).timeout) {
clearTimeout((this.element as any).timeout);
}
const component = window.resolveReactComponent(this.componentValue);
this._renderReactElement(React.createElement(component, this.propsValue, null));

(this.element as any).timeout = setTimeout(() => {
const component = window.resolveReactComponent(this.componentValue);
this._renderReactElement(React.createElement(component, this.propsValue, null));

this._dispatchEvent('react:mount', {
componentName: this.componentValue,
component: component,
props: this.propsValue,
});
}, 50);
this._dispatchEvent('react:mount', {
componentName: this.componentValue,
component: component,
props: this.propsValue,
});
}

disconnect() {
(this.element as any).unmount();
(this.element as any).root.unmount();
this._dispatchEvent('react:unmount', { component: this.componentValue, props: this.propsValue });
}

_renderReactElement(reactElement: ReactElement) {
if (parseInt(React.version) >= 18) {
// React 18+ rendering
// eslint-disable-next-line @typescript-eslint/no-var-requires
const root = require('react-dom/client').createRoot(this.element);
root.render(reactElement);

// Register a way to unmount this element
(this.element as any).unmount = () => {
root.unmount();
};

return;
}

// Up to React 17 rendering
// eslint-disable-next-line @typescript-eslint/no-var-requires
const reactDom = require('react-dom');
reactDom.render(reactElement, this.element);
const root = createRoot(this.element);
root.render(reactElement);

// Register a way to unmount this element
(this.element as any).unmount = () => {
reactDom.unmountComponentAtNode(this.element);
};
(this.element as any).root = root;
}

_dispatchEvent(name: string, payload: any) {
Expand Down
88 changes: 87 additions & 1 deletion src/React/Resources/doc/index.rst
Original file line number Diff line number Diff line change
@@ -1,4 +1,90 @@
Symfony UX React
================

TODO
Symfony UX React is a Symfony bundle integrating `React`_ in
Symfony applications. It is part of `the Symfony UX initiative`_.

React is a JavaScript library for building user interfaces.
Symfony UX React provides tools to render React components from Twig,
handling rendering and data transfers.

Symfony UX React supports React 18+.

Installation
------------

Before you start, make sure you have `Symfony UX configured in your app`_.

Then install the bundle using Composer and Symfony Flex:

.. code-block:: terminal
$ composer require symfony/ux-react
# Don't forget to install the JavaScript dependencies as well and compile
$ yarn install --force
$ yarn encore dev
You also need to add the following lines at the end to your ``assets/app.js`` file:

.. code-block:: javascript
// assets/app.js
import { registerReactControllerComponents } from '@symfony/ux-react';
// Registers React controller components to allow loading them from Twig
//
// React controller components are components that are meant to be rendered
// from Twig. These component then rely on other components that won't be called
// directly from Twig.
//
// By putting only controller components in `react/controllers`, you ensure that
// internal components won't be automatically included in your JS built file if
// they are not necessary.
registerReactControllerComponents(require.context('./react/controllers', true, /\.(j|t)sx?$/));
Usage
-----

UX React works by using a system of **React controller components**: React components that
are registered using ``registerReactControllerComponents`` and that are meant to be rendered
from Twig.

When using the ``registerReactControllerComponents`` configuration shown previously, all
React components located in the directory ``assets/react/controllers`` are registered as
React controller components.

You can then render any React controller component in Twig using the ``react_component``.
For example:

.. code-block:: javascript
// assets/react/controllers/MyComponent.jsx
import React from 'react';
export default function (props) {
return <div>Hello {props.fullName}</div>;
}
.. code-block:: twig
{# templates/home.html.twig #}
<div {{ react_component('MyComponent', { 'fullName': app.user.fullName }) }}></div>
Backward Compatibility promise
------------------------------

This bundle aims at following the same Backward Compatibility promise as
the Symfony framework:
https://symfony.com/doc/current/contributing/code/bc.html

However it is currently considered `experimental`_,
meaning it is not bound to Symfony's BC policy for the moment.

.. _`React`: https://reactjs.org/
.. _`the Symfony UX initiative`: https://symfony.com/ux
.. _`experimental`: https://symfony.com/doc/current/contributing/code/experimental.html
.. _`Symfony UX configured in your app`: https://symfony.com/doc/current/frontend/ux.html

0 comments on commit 4059895

Please sign in to comment.