SlideShare una empresa de Scribd logo
deSymfony 16-17 septiembre 2016
Madrid
INTEGRANDO REACT.JS
EN APLICACIONES
SYMFONY
Nacho Martín
deSymfony
¡Muchas gracias a nuestros
patrocinadores!
Programo en Limenius
Casi todos los proyectos
necesitan un frontend rico, por
una razón o por otra
Hacemos aplicaciones a medida
Así que le hemos dado unas cuantas vueltas
Objetivo:
Mostrar cosas que nos
encontramos al usar React desde
Symfony, en tierra de nadie, y que
ninguno de los dos va a
documentar.
¿A mí qué me importa el
frontend?
Integrando React.js en aplicaciones Symfony (deSymfony 2016)
¿Qué es React.js?
Echar huevos en sartén
La premisa fundamental
Cómo hacer una tortilla
Comprar huevos
Romper huevos
Echar huevos en sartén
Batir huevos
La premisa fundamental
Cómo hacer una tortilla
Comprar huevos
Romper huevos
Opciones:
La premisa fundamental
Opciones:
La premisa fundamental
1: Repintamos todo.
Opciones:
La premisa fundamental
1: Repintamos todo. Simple
Opciones:
La premisa fundamental
1: Repintamos todo. Simple Poco eficiente
Opciones:
2: Buscamos en el DOM
dónde insertar, qué
mover, qué eliminar.
La premisa fundamental
1: Repintamos todo. Simple Poco eficiente
Opciones:
2: Buscamos en el DOM
dónde insertar, qué
mover, qué eliminar.
La premisa fundamental
1: Repintamos todo. Simple
Complejo
Poco eficiente
Opciones:
2: Buscamos en el DOM
dónde insertar, qué
mover, qué eliminar.
La premisa fundamental
1: Repintamos todo. Simple
Muy eficienteComplejo
Poco eficiente
Opciones:
2: Buscamos en el DOM
dónde insertar, qué
mover, qué eliminar.
La premisa fundamental
1: Repintamos todo. Simple
Muy eficienteComplejo
Poco eficiente
React nos permite hacer 1, aunque en la sombra hace 2
Dame un estado y una función render() que
dependa de él, y olvídate de cómo y cuándo pinto.*
La premisa fundamental
Dame un estado y una función render() que
dependa de él, y olvídate de cómo y cuándo pinto.*
La premisa fundamental
* A menos que quieras tener control absoluto.
¡Clícame! Clicks: 0
Nuestro primer componente
¡Clícame! Clicks: 1
Nuestro primer componente
¡Clícame!
Nuestro primer componente
import React, { Component } from 'react';
class Counter extends Component {
constructor(props) {
super(props);
this.state = {count: 1};
}
tick() {
this.setState({count: this.state.count + 1});
}
render() {
return (
<div className="App">
<button onClick={this.tick.bind(this)}>Clícame!</button>
<span>Clicks: {this.state.count}</span>
</div>
);
}
}
export default Counter;
Nuestro primer componente
import React, { Component } from 'react';
class Counter extends Component {
constructor(props) {
super(props);
this.state = {count: 1};
}
tick() {
this.setState({count: this.state.count + 1});
}
render() {
return (
<div className="App">
<button onClick={this.tick.bind(this)}>Clícame!</button>
<span>Clicks: {this.state.count}</span>
</div>
);
}
}
export default Counter;
Sintaxis ES6 (opcional)
Nuestro primer componente
import React, { Component } from 'react';
class Counter extends Component {
constructor(props) {
super(props);
this.state = {count: 1};
}
tick() {
this.setState({count: this.state.count + 1});
}
render() {
return (
<div className="App">
<button onClick={this.tick.bind(this)}>Clícame!</button>
<span>Clicks: {this.state.count}</span>
</div>
);
}
}
export default Counter;
Sintaxis ES6 (opcional)
Estado inicial
Nuestro primer componente
import React, { Component } from 'react';
class Counter extends Component {
constructor(props) {
super(props);
this.state = {count: 1};
}
tick() {
this.setState({count: this.state.count + 1});
}
render() {
return (
<div className="App">
<button onClick={this.tick.bind(this)}>Clícame!</button>
<span>Clicks: {this.state.count}</span>
</div>
);
}
}
export default Counter;
Sintaxis ES6 (opcional)
Modificar estado
Estado inicial
Nuestro primer componente
import React, { Component } from 'react';
class Counter extends Component {
constructor(props) {
super(props);
this.state = {count: 1};
}
tick() {
this.setState({count: this.state.count + 1});
}
render() {
return (
<div className="App">
<button onClick={this.tick.bind(this)}>Clícame!</button>
<span>Clicks: {this.state.count}</span>
</div>
);
}
}
export default Counter;
Sintaxis ES6 (opcional)
Modificar estado
render(), lo llama React
Estado inicial
Trabajar con el estado
Trabajar con el estado
constructor(props) {
super(props);
this.state = {count: 1};
}
Estado inicial
Trabajar con el estado
constructor(props) {
super(props);
this.state = {count: 1};
}
Estado inicial
this.setState({count: this.state.count + 1});
Asignar estado
Trabajar con el estado
constructor(props) {
super(props);
this.state = {count: 1};
}
Estado inicial
this.setState({count: this.state.count + 1});
Asignar estado
this.state.count = this.state.count + 1;
Simplemente recordar evitar
render() y JSX
render() {
return (
<div className="App">
<button onClick={this.tick.bind(this)}>Clícame!</button>
<span>Clicks: {this.state.count}</span>
</div>
);
}
No es HTML, es JSX.
React lo transforma internamente a elementos.
Buena práctica: Dejar render() lo más limpio posible,
solo un return.
render() y JSX
render() {
return (
<div className="App">
<button onClick={this.tick.bind(this)}>Clícame!</button>
<span>Clicks: {this.state.count}</span>
</div>
);
}
No es HTML, es JSX.
React lo transforma internamente a elementos.
Algunas cosas cambian
Buena práctica: Dejar render() lo más limpio posible,
solo un return.
render() y JSX
render() {
return (
<div className="App">
<button onClick={this.tick.bind(this)}>Clícame!</button>
<span>Clicks: {this.state.count}</span>
</div>
);
}
No es HTML, es JSX.
React lo transforma internamente a elementos.
Algunas cosas cambian
Entre {} podemos insertar expresiones JS
Buena práctica: Dejar render() lo más limpio posible,
solo un return.
Thinking in React
render() {
return (
<div className="App">
<button onClick={this.tick.bind(this)}>Clícame!</button>
<span>Clicks: {this.state.count}</span>
</div>
);
}
Thinking in React
render() {
return (
<div className="App">
<button onClick={this.tick.bind(this)}>Clícame!</button>
<span>Clicks: {this.state.count}</span>
</div>
);
}
Aquí no modificar el estado
Thinking in React
render() {
return (
<div className="App">
<button onClick={this.tick.bind(this)}>Clícame!</button>
<span>Clicks: {this.state.count}</span>
</div>
);
}
Aquí no Ajax
Thinking in React
render() {
return (
<div className="App">
<button onClick={this.tick.bind(this)}>Clícame!</button>
<span>Clicks: {this.state.count}</span>
</div>
);
}
Aquí no calcular decimales de pi y enviar un email
Importante: pensar la jerarquía
Importante: pensar la jerarquía
Jerarquía de componentes: props
class CounterGroup extends Component {
render() {
return (
<div>
<Counter name="amigo"/>
<Counter name="señor"/>
</div>
);
}
}
render() {
return (
<div className="App">
<button onClick={this.tick.bind(this)}>
Clícame {this.props.name}
</button>
<span>Clicks: {this.state.count}</span>
</div>
);
}
y en Counter…
Jerarquía de componentes: props
class CounterGroup extends Component {
render() {
return (
<div>
<Counter name="amigo"/>
<Counter name="señor"/>
</div>
);
}
}
render() {
return (
<div className="App">
<button onClick={this.tick.bind(this)}>
Clícame {this.props.name}
</button>
<span>Clicks: {this.state.count}</span>
</div>
);
}
y en Counter…
Jerarquía de componentes: props
class CounterGroup extends Component {
render() {
return (
<div>
<Counter name="amigo"/>
<Counter name="señor"/>
</div>
);
}
}
Pro tip: componentes sin estado
const Saludador = (props) => {
<div>
<div>Hola {props.name}</div>
</div>
}
Todo depende del estado, por tanto:
Todo depende del estado, por tanto:
•Podemos reproducir estados,
Todo depende del estado, por tanto:
•Podemos reproducir estados,
•rebobinar,
Todo depende del estado, por tanto:
•Podemos reproducir estados,
•rebobinar,
•loguear cambios de estado,
Todo depende del estado, por tanto:
•Podemos reproducir estados,
•rebobinar,
•loguear cambios de estado,
•hacer álbum de estilo
Todo depende del estado, por tanto:
•Podemos reproducir estados,
•rebobinar,
•loguear cambios de estado,
•hacer álbum de estilo
•…
Learn once,
write everywhere
¿Y si en lugar de algo así…
render() {
return (
<div className="App">
<button onClick={this.tick.bind(this)}>Clícame!</button>
<span>Clicks: {this.state.count}</span>
</div>
);
}
…tenemos algo así?
render () {
return (
<View>
<ListView
dataSource={dataSource}
renderRow={(rowData) =>
<TouchableOpacity >
<View>
<Text>{rowData.name}</Text>
<View>
<SwitchIOS
onValueChange={(value) =>
this.setMissing(item, value)}
value={item.missing} />
</View>
</View>
</TouchableOpacity>
}
/>
</View>
);
}
…tenemos algo así?
render () {
return (
<View>
<ListView
dataSource={dataSource}
renderRow={(rowData) =>
<TouchableOpacity >
<View>
<Text>{rowData.name}</Text>
<View>
<SwitchIOS
onValueChange={(value) =>
this.setMissing(item, value)}
value={item.missing} />
</View>
</View>
</TouchableOpacity>
}
/>
</View>
);
}
React Native
React Targets
•Web - react-dom
•Mobile - React Native
•Gl shaders - gl-react
•Canvas - react-canvas
•Terminal - react-blessed
react-blessed (terminal)
Setup
¿Assetic?
Setup recomendado
Setup recomendado
Webpack
Pros
Webpack
• Gestiona dependencias por nosotros.
Pros
Webpack
• Gestiona dependencias por nosotros.
• Permite varios entornos: producción, desarrollo, ….
Pros
Webpack
• Gestiona dependencias por nosotros.
• Permite varios entornos: producción, desarrollo, ….
• Recarga automática de página (e incluso hot reload).
Pros
Webpack
• Gestiona dependencias por nosotros.
• Permite varios entornos: producción, desarrollo, ….
• Recarga automática de página (e incluso hot reload).
• Uso de preprocesadores/“transpiladores”, como Babel.
Pros
Webpack
• Gestiona dependencias por nosotros.
• Permite varios entornos: producción, desarrollo, ….
• Recarga automática de página (e incluso hot reload).
• Uso de preprocesadores/“transpiladores”, como Babel.
• Los programadores de frontend no nos arquearán la ceja.
Pros
Webpack
• Gestiona dependencias por nosotros.
• Permite varios entornos: producción, desarrollo, ….
• Recarga automática de página (e incluso hot reload).
• Uso de preprocesadores/“transpiladores”, como Babel.
• Los programadores de frontend no nos arquearán la ceja.
Pros
Contras
Webpack
• Gestiona dependencias por nosotros.
• Permite varios entornos: producción, desarrollo, ….
• Recarga automática de página (e incluso hot reload).
• Uso de preprocesadores/“transpiladores”, como Babel.
• Los programadores de frontend no nos arquearán la ceja.
Pros
Contras
• Tiene su curva de aprendizaje.
Webpack
• Gestiona dependencias por nosotros.
• Permite varios entornos: producción, desarrollo, ….
• Recarga automática de página (e incluso hot reload).
• Uso de preprocesadores/“transpiladores”, como Babel.
• Los programadores de frontend no nos arquearán la ceja.
Pros
Contras
• Tiene su curva de aprendizaje.
Ejemplo completo: https://github.com/Limenius/symfony-react-sandbox
Inserción
<div id="react-placeholder"></div>
import ReactDOM from 'react-dom';
ReactDOM.render(
<Counter name="amigo">,
document.getElementById('react-placeholder')
);
HTML
JavaScript
Integración con Symfony
https://github.com/Limenius/ReactBundle
https://github.com/shakacode/react_on_rails
Basado en
ReactBundle
{{ react_component('RecipesApp', {'props': props}) }}
import ReactOnRails from 'react-on-rails';
import RecipesApp from './RecipesAppServer';
ReactOnRails.register({ RecipesApp });
Twig:
JavaScript:
ReactBundle
{{ react_component('RecipesApp', {'props': props}) }}
import ReactOnRails from 'react-on-rails';
import RecipesApp from './RecipesAppServer';
ReactOnRails.register({ RecipesApp });
Twig:
JavaScript:
ReactBundle
{{ react_component('RecipesApp', {'props': props}) }}
import ReactOnRails from 'react-on-rails';
import RecipesApp from './RecipesAppServer';
ReactOnRails.register({ RecipesApp });
Twig:
JavaScript:
ReactBundle
{{ react_component('RecipesApp', {'props': props}) }}
import ReactOnRails from 'react-on-rails';
import RecipesApp from './RecipesAppServer';
ReactOnRails.register({ RecipesApp });
Twig:
JavaScript:
<div class="js-react-on-rails-component" style="display:none" data-
component-name=“RecipesApp” data-props=“[mi Array en JSON]" data-
trace=“false" data-dom-id=“sfreact-57d05640f2f1a”></div>
HTML generado:
Aplicaciones universales
Integrando React.js en aplicaciones Symfony (deSymfony 2016)
Dame un estado y una función render() que
dependa de él, y olvídate de cómo y cuándo pinto.
La premisa fundamental
Dame un estado y una función render() que
dependa de él, y olvídate de cómo y cuándo pinto.
La premisa fundamental
Podemos renderizar componentes desde el servidor.
Dame un estado y una función render() que
dependa de él, y olvídate de cómo y cuándo pinto.
La premisa fundamental
Podemos renderizar componentes desde el servidor.
• SEO friendly.
Dame un estado y una función render() que
dependa de él, y olvídate de cómo y cuándo pinto.
La premisa fundamental
Podemos renderizar componentes desde el servidor.
• SEO friendly.
• Carga de página más rápida.
Dame un estado y una función render() que
dependa de él, y olvídate de cómo y cuándo pinto.
La premisa fundamental
Podemos renderizar componentes desde el servidor.
• SEO friendly.
• Carga de página más rápida.
• Podemos cachear.
Client-side
{{ react_component('RecipesApp', {'props': props,
'rendering': 'client-side'}}) }}
TWIG
Client-side
{{ react_component('RecipesApp', {'props': props,
'rendering': 'client-side'}}) }}
TWIG
Client-side
{{ react_component('RecipesApp', {'props': props,
'rendering': 'client-side'}}) }}
TWIG
<div class="js-react-on-rails-component" style="display:none" data-
component-name=“RecipesApp” data-props=“…” data-dom-
id=“sfreact-57d05640f2f1a”></div>
HTML que devuelve el servidor
Client-side
{{ react_component('RecipesApp', {'props': props,
'rendering': 'client-side'}}) }}
TWIG
<div class="js-react-on-rails-component" style="display:none" data-
component-name=“RecipesApp” data-props=“…” data-dom-
id=“sfreact-57d05640f2f1a”></div>
HTML que devuelve el servidor
Generado en el navegador
<div id="sfreact-57d05640f2f1a"><div data-reactroot="" data-
reactid="1" data-react-checksum="2107256409"><ol class="breadcrumb"
data-reactid="2"><li class="active" data-reactid=“3">Recipes</li>
…
…
</div>
Client-side y server-side
{{ react_component('RecipesApp', {'props': props,
'rendering': 'both'}}) }}
TWIG
Client-side y server-side
{{ react_component('RecipesApp', {'props': props,
'rendering': 'both'}}) }}
TWIG
Client-side y server-side
{{ react_component('RecipesApp', {'props': props,
'rendering': 'both'}}) }}
TWIG
HTML que devuelve el servidor
<div id="sfreact-57d05640f2f1a"><div data-reactroot="" data-
reactid="1" data-react-checksum="2107256409"><ol class="breadcrumb"
data-reactid="2"><li class="active" data-reactid=“3">Recipes</li>
…
…
</div>
Client-side y server-side
{{ react_component('RecipesApp', {'props': props,
'rendering': 'both'}}) }}
TWIG
HTML que devuelve el servidor
<div id="sfreact-57d05640f2f1a"><div data-reactroot="" data-
reactid="1" data-react-checksum="2107256409"><ol class="breadcrumb"
data-reactid="2"><li class="active" data-reactid=“3">Recipes</li>
…
…
</div>
Y React en el navegador toma el control
al evaluar el código
Aplicaciones universales: Opciones
Opción 1: llamar a subproceso node.js
Llamamos a node.js con el componente Process de
Symfony
* Cómodo (si tenemos node.js instalado).
* Lento.
Librería: https://github.com/nacmartin/phpexecjs
Opción 2: v8js
Usamos la extensión de PHP v8js
* Cómodo (aunque puede que haya que compilar la
extensión y v8).
* Lento por ahora, potencialmente podríamos tener
v8 precargada usando php-pm.
Librería: https://github.com/nacmartin/phpexecjs
Configuración Opciones 1 y 2
limenius_react:
serverside_rendering:
mode: "phpexecjs"
phpexecjs detecta si tenemos la extensión v8js,
y si no llama a node.js
config.yml:
Opción 3: Servidor externo
Tenemos un servidor node.js “tonto” que nos
renderiza React.
Es un servidor de <100 líneas, que es independiente
de nuestra lógica.
* “Incómodo” (hay que mantener el servidor node.js
corriendo, que tampoco es para morirse).
* Rápido.
Ver ejemplo en
https://github.com/Limenius/symfony-react-sandbox
Configuración Opción 3
limenius_react:
serverside_rendering:
mode: “external”
server-socket-path: “../tal_y_tal/node.sock”
config.yml:
Lo mejor de los dos mundos
En desarrollo usar llamada a node.js o v8js con
phpexecjs.
En producción tener un servidor externo.
Si podemos cachear, menos problema.
Es decir:
limenius_react:
serverside_rendering:
mode: “external”
server-socket-path: “../tal_y_tal/node.sock”
config.yml:
limenius_react:
serverside_rendering:
mode: "phpexecjs"
config_dev.yml:
¿Vale la pena una app universal?
¿Vale la pena una app universal?
En ocasiones sí, pero introduce
complejidad.
Soporte para Redux
(+brevísima introducción a Redux)
Redux: una cuestión de estado
guardar
Tu nombre: Juan
Hola, Juan
Cosas de Juan:
Redux: una cuestión de estado
guardar
Tu nombre: Juan
Hola, Juan
Cosas de Juan:
Redux: una cuestión de estado
guardar
Tu nombre: Juan
Hola, Juan
Cosas de Juan:
state.name
callback para cambiarlo
dispatch(changeName(‘Juan'));
Componente
dispatch(changeName(‘Juan'));
Componente
changeName = (name) => {
return {
type: ‘CHANGE_NAME',
name
}
}
Action
dispatch(changeName(‘Juan'));
Componente
changeName = (name) => {
return {
type: ‘CHANGE_NAME',
name
}
}
Action
const todo = (state = {name: null}, action) => {
switch (action.type) {
case 'CHANGE_USER':
return {
name: action.name
}
}
}
Reducer
dispatch(changeName(‘Juan'));
Componente
changeName = (name) => {
return {
type: ‘CHANGE_NAME',
name
}
}
Action
const todo = (state = {name: null}, action) => {
switch (action.type) {
case 'CHANGE_USER':
return {
name: action.name
}
}
}
Reducer
Store
this.props.name == ‘Juan';dispatch(changeName(‘Juan'));
Componente
changeName = (name) => {
return {
type: ‘CHANGE_NAME',
name
}
}
Action
const todo = (state = {name: null}, action) => {
switch (action.type) {
case 'CHANGE_USER':
return {
name: action.name
}
}
}
Reducer
Store
Redux en ReactBundle
Ver ejemplo en
https://github.com/Limenius/symfony-react-sandbox
import ReactOnRails from 'react-on-rails';
import RecipesApp from './RecipesAppClient';
import recipesStore from '../store/recipesStore';
ReactOnRails.registerStore({recipesStore})
ReactOnRails.register({ RecipesApp });
Twig:
JavaScript:
{{ redux_store('recipesStore', props) }}
{{ react_component('RecipesApp') }}
Redux en ReactBundle
Ver ejemplo en
https://github.com/Limenius/symfony-react-sandbox
import ReactOnRails from 'react-on-rails';
import RecipesApp from './RecipesAppClient';
import recipesStore from '../store/recipesStore';
ReactOnRails.registerStore({recipesStore})
ReactOnRails.register({ RecipesApp });
Twig:
JavaScript:
{{ redux_store('recipesStore', props) }}
{{ react_component('RecipesApp') }}
{{ react_component('OtroComponente') }}
Compartir store entre componentes
Compartir store entre componentes
React
React
React
Twig
Twig
React
Al compartir store comparten estado
Twig
Formularios, un caso especial
Formularios muy dinámicos
•Dentro de aplicaciones React.
•Formularios importantísimos en los que un detalle
de usabilidad vale mucho dinero.
•Formularios muy específicos.
•Formularios muy dinámicos que no cansen (ver
typeform por ejemplo).
Integrando React.js en aplicaciones Symfony (deSymfony 2016)
El problema
Supongamos un form así
public function buildForm(FormBuilderInterface $builder, array
$options)
{
$builder
->add('country', ChoiceType::class, [
'choices' => [
'España' => 'es',
'Portugal' => 'pt',
]
])
->add('addresses', CollectionType::class, ...);
};
Forms en HTML
$form->createView();
state.usuario
Forms en HTML
$form->createView();
state.usuario
Forms en HTML
$form->createView();
submit
país: España
España
Portugal
direcciones:
C tal-
+state.usuario
Forms en HTML
$form->createView();
submit
país: España
España
Portugal
direcciones:
C tal-
+state.usuario
Forms en HTML
$form->createView();
submit
país: España
España
Portugal
direcciones:
C tal-
+state.usuario
POST bien formadito
con country:’es’
y no ‘España’, ‘espana', ‘spain', ‘0’…
Forms en HTML
$form->createView();
$form->submit($request);
submit
país: España
España
Portugal
direcciones:
C tal-
+state.usuario
POST bien formadito
con country:’es’
y no ‘España’, ‘espana', ‘spain', ‘0’…
Forms en API
$form;
state.usuario
Forms en API
$form;
submit
país: España
España
Portugal
direcciones:
C tal-
+state.usuario
✘
Forms en API
$form;
submit
país: España
España
Portugal
direcciones:
C tal-
+state.usuario
✘
¿Cómo sabemos los campos,
o los choices?
¡A documentar! :(
Forms en API
$form;
$form->submit($request);
submit
país: España
España
Portugal
direcciones:
C tal-
+state.usuario
POST “voy a tener suerte”
✘
¿Cómo sabemos los campos,
o los choices?
¡A documentar! :(
Forms en API
$form;
$form->submit($request);
submit
país: España
España
Portugal
direcciones:
C tal-
+state.usuario
POST “voy a tener suerte”
✘
¿Cómo sabemos los campos,
o los choices?
¡A documentar! :(
This form should not contain extra fields!!1
Forms en API
$form;
$form->submit($request);
submit
país: España
España
Portugal
direcciones:
C tal-
+state.usuario
POST “voy a tener suerte”
✘
¿Cómo sabemos los campos,
o los choices?
¡A documentar! :(
This form should not contain extra fields!!1
The value you selected is not a valid choice!!
Forms en API
$form;
$form->submit($request);
submit
país: España
España
Portugal
direcciones:
C tal-
+state.usuario
POST “voy a tener suerte”
✘
¿Cómo sabemos los campos,
o los choices?
¡A documentar! :(
This form should not contain extra fields!!1
The value you selected is not a valid choice!!One or more of the given values is invalid!! :D
Forms en API
$form;
$form->submit($request);
submit
país: España
España
Portugal
direcciones:
C tal-
+state.usuario
POST “voy a tener suerte”
✘
¿Cómo sabemos los campos,
o los choices?
¡A documentar! :(
This form should not contain extra fields!!1
The value you selected is not a valid choice!!One or more of the given values is invalid!! :DMUHAHAHAHAHA!!!!!
Integrando React.js en aplicaciones Symfony (deSymfony 2016)
Integrando React.js en aplicaciones Symfony (deSymfony 2016)
Definir (y mantener) por triplicado
Form SF API docs Form Cliente
:(
¿Cuántos programadores hacen falta para hacer un
formulario?
Caso: Wizard complejo
Caso: Wizard complejo
Caso: Wizard complejo
Lo que necesitamos
$form->createView();
HTML
API
$miTransformador->transform($form);
Lo que necesitamos
$form->createView();
HTML
¡Serializar! Vale, ¿A qué formato?
API
$miTransformador->transform($form);
JSON Schema
Integrando React.js en aplicaciones Symfony (deSymfony 2016)
Qué pinta tiene
{
"$schema": "http://json-schema.org/draft-04/schema#",
"title": "Product",
"description": "A product from Acme's catalog",
"type": "object",
"properties": {
"name": {
"description": "Name of the product",
"type": "string"
},
"price": {
"type": "number",
"minimum": 0,
"exclusiveMinimum": true
},
"tags": {
"type": "array",
"items": {
"type": "string"
},
"minItems": 1,
"uniqueItems": true
}
},
"required": ["id", "name", "price"]
}
definiciones,
tipos,
reglas de validación
:)
Nuevo recurso: mi-api/products/form
A partir del schema generamos form
Generadores client-side
• jdorn/json-editor: no React, es un veterano.
• mozilla/react-jsonschema-form: React. creado
por un antiguo Symfonero (Niko Perriault).
• limenius/liform-react: React + redux; integrado
con redux-form (I ♥ redux-form)
• …
• Crear el nuestro puede ser conveniente.
Generadores client-side: diferencias
Cada uno amplía json-schema a su manera para
especificar detalles UI: Orden de campos, qué widget
específico usar, etc.
Si queremos usarlos al máximo hay que generar
un json-schema específico para cada uno
(no son totalmente intercambiables).
Ejemplo: usar editor Wysiwyg en un campo texto
Ejemplo: LiformBundle y liform-react
limenius/LiformBundle: Genera json-schema a partir
de formularios Symfony.
limenius/liform-react: Generador de formularios
React (con redux-form) a partir de json-schema.
Son Work in progress
Cómo serializar: resolvers + transformers
$transformer = $resolver->resolve($form);
$jsonSchema = $transformer->transform($form);
Ejemplo de esta técnica: https://github.com/Limenius/LiformBundle
Resolver
public function resolve(FormInterface $form)
{
$types = FormUtil::typeAncestry($form);
foreach ($types as $type) {
if (isset($this->transformers[$type])) {
return $this->transformers[$type];
}
}
}
Misión: Encuentra el transformer apropiado para el form
Transformer
Misión: Inspecciona el form y crea un array.
Si es compuesto resuelve+transforma los hijos.
class IntegerTransformer extends AbstractTransformer
{
public function transform(FormInterface $form)
{
$schema = [
'type' => 'integer',
];
if ($liform = $form->getConfig()->getOption('liform')) {
if ($format = $liform['format']) {
$schema['format'] = $format;
}
}
$this->addCommonSpecs($form, $schema);
return $schema;
}
}
protected function addLabel($form, &$schema)
{
if ($label = $form->getConfig()->getOption('label')) {
$schema['title'] = $label;
}
}
Transformer
Recopila información de cada Form Field.
Podemos sacar mucha información:
•Valores por defecto & placeholders.
•Atributos del formulario.
•Validadores.
Ejemplo: validación ‘pattern’
protected function addPattern($form, &$schema)
{
if ($attr = $form->getConfig()->getOption('attr')) {
if (isset($attr['pattern'])) {
$schema['pattern'] = $attr['pattern'];
}
}
}
Esta técnica vale también
para Angular, Backbone,
mobile…
Repaso:
Repaso:
• Qué es React
• Setup
• Apps universales (ReactBundle)
• Para qué sirve Redux
• El problema de los formularios
• Cómo aliviarlo con JSON Schema
MADRID · NOV 27-28 · 2015
¡Gracias!
@nacmartin
nacho@limenius.com
http://limenius.com
Formación, consultoría
y desarrollo de proyectos

Más contenido relacionado

PDF
Automated Discovery of Deserialization Gadget Chains
PPT
Mô hình 3 lớp
DOCX
Php interview questions
PDF
Working with deeply nested documents in Apache Solr
PDF
[2019] Spring JPA의 사실과 오해
PPT
Introduction to XML
PPTX
CollabSphere SC 103 : Domino on the Web : Yes, It's (Probably) Hackable
PPTX
Introduction to PostgreSQL
Automated Discovery of Deserialization Gadget Chains
Mô hình 3 lớp
Php interview questions
Working with deeply nested documents in Apache Solr
[2019] Spring JPA의 사실과 오해
Introduction to XML
CollabSphere SC 103 : Domino on the Web : Yes, It's (Probably) Hackable
Introduction to PostgreSQL

La actualidad más candente (20)

PPTX
Apache http server
PDF
HTTP Security Headers
PPTX
noSQL
PDF
Sql Injection
DOCX
Data Warehousing Practical for T.Y.I.T.
PDF
PPTX
NoSQL Data Stores in Research and Practice - ICDE 2016 Tutorial - Extended Ve...
PDF
Vm escape: case study virtualbox bug hunting and exploitation - Muhammad Alif...
PDF
How to Prevent RFI and LFI Attacks
PDF
Cours JavaScript
PDF
An introduction to cgroups and cgroupspy
PDF
WordPress 1.pdf
PDF
Rapport DVWA: CSRF
PDF
[pgday.Seoul 2022] PostgreSQL구조 - 윤성재
PPTX
Huong dan dung index_oracle
PDF
MongoDB .local Toronto 2019: MongoDB Atlas Search Deep Dive
PPTX
TO Hack an ASP .NET website?
PDF
3 Layers of the Web - Part 1
PDF
Insights beyond Human Intuition: Comprehensively Mining Survey Data
Apache http server
HTTP Security Headers
noSQL
Sql Injection
Data Warehousing Practical for T.Y.I.T.
NoSQL Data Stores in Research and Practice - ICDE 2016 Tutorial - Extended Ve...
Vm escape: case study virtualbox bug hunting and exploitation - Muhammad Alif...
How to Prevent RFI and LFI Attacks
Cours JavaScript
An introduction to cgroups and cgroupspy
WordPress 1.pdf
Rapport DVWA: CSRF
[pgday.Seoul 2022] PostgreSQL구조 - 윤성재
Huong dan dung index_oracle
MongoDB .local Toronto 2019: MongoDB Atlas Search Deep Dive
TO Hack an ASP .NET website?
3 Layers of the Web - Part 1
Insights beyond Human Intuition: Comprehensively Mining Survey Data
Publicidad

Destacado (20)

PDF
Finally, Professional Frontend Dev with ReactJS, WebPack & Symfony (Symfony C...
PDF
Refactorizando Pccomponentes.com con Symfony
PDF
Integrating React.js Into a PHP Application
PDF
Symfony Guard Authentication: Fun with API Token, Social Login, JWT and more
PDF
Decoupling the Ulabox.com monolith. From CRUD to DDD
PDF
Handling 10k requests per second with Symfony and Varnish - SymfonyCon Berlin...
PDF
When cqrs meets event sourcing
PDF
Desarrollo código mantenible en WordPress utilizando Symfony
PDF
Multi kernelowa aplikacja w oparciu o Symfony 3 i microkernele
PDF
Introducción al ecosistema de React.js
PDF
From * to Symfony2
PDF
SaaS con Symfony2
PDF
Scaling symfony apps
PDF
Symfony day 2016
PDF
Getting your open source company to contribution
PDF
Doctrine2 sf2Vigo
PDF
WordCamp Cantabria - Código mantenible con WordPress
PDF
Nuvola: a tale of migration to AWS
PDF
How Symfony Changed My Life
PDF
You Got React.js in My PHP
Finally, Professional Frontend Dev with ReactJS, WebPack & Symfony (Symfony C...
Refactorizando Pccomponentes.com con Symfony
Integrating React.js Into a PHP Application
Symfony Guard Authentication: Fun with API Token, Social Login, JWT and more
Decoupling the Ulabox.com monolith. From CRUD to DDD
Handling 10k requests per second with Symfony and Varnish - SymfonyCon Berlin...
When cqrs meets event sourcing
Desarrollo código mantenible en WordPress utilizando Symfony
Multi kernelowa aplikacja w oparciu o Symfony 3 i microkernele
Introducción al ecosistema de React.js
From * to Symfony2
SaaS con Symfony2
Scaling symfony apps
Symfony day 2016
Getting your open source company to contribution
Doctrine2 sf2Vigo
WordCamp Cantabria - Código mantenible con WordPress
Nuvola: a tale of migration to AWS
How Symfony Changed My Life
You Got React.js in My PHP
Publicidad

Similar a Integrando React.js en aplicaciones Symfony (deSymfony 2016) (20)

PDF
Tutorial de ReactJS.pdf completo para principiantes
PPTX
React, Flux y React native
PPTX
Introducción a react + redux
PPTX
Cross development - React para desarrolladores de asp.net
PPTX
CrossDvlpu - REACT para desarrolladores de ASP.NET
PDF
PPTX
Conociendo ReactJs . Scio Talks
PDF
Reactvolution
PPTX
Desarrollo Back-end Con React y node.pptx
PPTX
Desarrollo de Aplicaciones N° 01 - 02 - reactjs(2).pptx
PPTX
React-Framework Exposicion sobre React, composiciones etc.
DOCX
Desarrollo de los diferentes hooks en react.docx
PDF
React redux workshop
PPTX
Material Apoyo ReactJS U- de caldas.pptx
PPTX
Introducción al uso de hooks en React js
PPT
ReactJS + ReactNative = React Developer
PDF
Intro a React - Piero Divasto
PDF
React - Drupal Camp 2016
PDF
React redux
Tutorial de ReactJS.pdf completo para principiantes
React, Flux y React native
Introducción a react + redux
Cross development - React para desarrolladores de asp.net
CrossDvlpu - REACT para desarrolladores de ASP.NET
Conociendo ReactJs . Scio Talks
Reactvolution
Desarrollo Back-end Con React y node.pptx
Desarrollo de Aplicaciones N° 01 - 02 - reactjs(2).pptx
React-Framework Exposicion sobre React, composiciones etc.
Desarrollo de los diferentes hooks en react.docx
React redux workshop
Material Apoyo ReactJS U- de caldas.pptx
Introducción al uso de hooks en React js
ReactJS + ReactNative = React Developer
Intro a React - Piero Divasto
React - Drupal Camp 2016
React redux

Más de Ignacio Martín (17)

PDF
Elixir/OTP for PHP developers
PDF
Introduction to React Native Workshop
PDF
Server side rendering with React and Symfony
PDF
Symfony 4 Workshop - Limenius
PDF
Server Side Rendering of JavaScript in PHP
PDF
Extending Redux in the Server Side
PDF
Redux Sagas - React Alicante
PDF
React Native Workshop - React Alicante
PDF
Asegurando APIs en Symfony con JWT
PDF
Redux saga: managing your side effects. Also: generators in es6
PDF
Integrating React.js with PHP projects
PDF
Introduction to Redux
PDF
Keeping the frontend under control with Symfony and Webpack
PDF
Adding Realtime to your Projects
PDF
Symfony & Javascript. Combining the best of two worlds
PDF
Symfony 2 CMF
PDF
Presentacion git
Elixir/OTP for PHP developers
Introduction to React Native Workshop
Server side rendering with React and Symfony
Symfony 4 Workshop - Limenius
Server Side Rendering of JavaScript in PHP
Extending Redux in the Server Side
Redux Sagas - React Alicante
React Native Workshop - React Alicante
Asegurando APIs en Symfony con JWT
Redux saga: managing your side effects. Also: generators in es6
Integrating React.js with PHP projects
Introduction to Redux
Keeping the frontend under control with Symfony and Webpack
Adding Realtime to your Projects
Symfony & Javascript. Combining the best of two worlds
Symfony 2 CMF
Presentacion git

Último (8)

DOCX
trabajo programacion.docxxdxxxddxdxxdxdxxxdxxdxdxd
PDF
AutoCAD Herramientas para el futuro, Juan Fandiño
PDF
simulacion de teoria de control para maquinas
PDF
modelos de control para sistemas digitales
PDF
DIMENSIONADO DE UNA INSTALACION FOTOVOLTAICA.pdf
PPTX
sistemas de informacion.................
PDF
Su punto de partida en la IA: Microsoft 365 Copilot Chat
PPTX
Derechos_de_Autor_y_Creative_Commons.pptx
trabajo programacion.docxxdxxxddxdxxdxdxxxdxxdxdxd
AutoCAD Herramientas para el futuro, Juan Fandiño
simulacion de teoria de control para maquinas
modelos de control para sistemas digitales
DIMENSIONADO DE UNA INSTALACION FOTOVOLTAICA.pdf
sistemas de informacion.................
Su punto de partida en la IA: Microsoft 365 Copilot Chat
Derechos_de_Autor_y_Creative_Commons.pptx

Integrando React.js en aplicaciones Symfony (deSymfony 2016)

  • 1. deSymfony 16-17 septiembre 2016 Madrid INTEGRANDO REACT.JS EN APLICACIONES SYMFONY Nacho Martín
  • 2. deSymfony ¡Muchas gracias a nuestros patrocinadores!
  • 3. Programo en Limenius Casi todos los proyectos necesitan un frontend rico, por una razón o por otra Hacemos aplicaciones a medida Así que le hemos dado unas cuantas vueltas
  • 4. Objetivo: Mostrar cosas que nos encontramos al usar React desde Symfony, en tierra de nadie, y que ninguno de los dos va a documentar.
  • 5. ¿A mí qué me importa el frontend?
  • 8. Echar huevos en sartén La premisa fundamental Cómo hacer una tortilla Comprar huevos Romper huevos
  • 9. Echar huevos en sartén Batir huevos La premisa fundamental Cómo hacer una tortilla Comprar huevos Romper huevos
  • 12. Opciones: La premisa fundamental 1: Repintamos todo. Simple
  • 13. Opciones: La premisa fundamental 1: Repintamos todo. Simple Poco eficiente
  • 14. Opciones: 2: Buscamos en el DOM dónde insertar, qué mover, qué eliminar. La premisa fundamental 1: Repintamos todo. Simple Poco eficiente
  • 15. Opciones: 2: Buscamos en el DOM dónde insertar, qué mover, qué eliminar. La premisa fundamental 1: Repintamos todo. Simple Complejo Poco eficiente
  • 16. Opciones: 2: Buscamos en el DOM dónde insertar, qué mover, qué eliminar. La premisa fundamental 1: Repintamos todo. Simple Muy eficienteComplejo Poco eficiente
  • 17. Opciones: 2: Buscamos en el DOM dónde insertar, qué mover, qué eliminar. La premisa fundamental 1: Repintamos todo. Simple Muy eficienteComplejo Poco eficiente React nos permite hacer 1, aunque en la sombra hace 2
  • 18. Dame un estado y una función render() que dependa de él, y olvídate de cómo y cuándo pinto.* La premisa fundamental
  • 19. Dame un estado y una función render() que dependa de él, y olvídate de cómo y cuándo pinto.* La premisa fundamental * A menos que quieras tener control absoluto.
  • 20. ¡Clícame! Clicks: 0 Nuestro primer componente
  • 21. ¡Clícame! Clicks: 1 Nuestro primer componente ¡Clícame!
  • 22. Nuestro primer componente import React, { Component } from 'react'; class Counter extends Component { constructor(props) { super(props); this.state = {count: 1}; } tick() { this.setState({count: this.state.count + 1}); } render() { return ( <div className="App"> <button onClick={this.tick.bind(this)}>Clícame!</button> <span>Clicks: {this.state.count}</span> </div> ); } } export default Counter;
  • 23. Nuestro primer componente import React, { Component } from 'react'; class Counter extends Component { constructor(props) { super(props); this.state = {count: 1}; } tick() { this.setState({count: this.state.count + 1}); } render() { return ( <div className="App"> <button onClick={this.tick.bind(this)}>Clícame!</button> <span>Clicks: {this.state.count}</span> </div> ); } } export default Counter; Sintaxis ES6 (opcional)
  • 24. Nuestro primer componente import React, { Component } from 'react'; class Counter extends Component { constructor(props) { super(props); this.state = {count: 1}; } tick() { this.setState({count: this.state.count + 1}); } render() { return ( <div className="App"> <button onClick={this.tick.bind(this)}>Clícame!</button> <span>Clicks: {this.state.count}</span> </div> ); } } export default Counter; Sintaxis ES6 (opcional) Estado inicial
  • 25. Nuestro primer componente import React, { Component } from 'react'; class Counter extends Component { constructor(props) { super(props); this.state = {count: 1}; } tick() { this.setState({count: this.state.count + 1}); } render() { return ( <div className="App"> <button onClick={this.tick.bind(this)}>Clícame!</button> <span>Clicks: {this.state.count}</span> </div> ); } } export default Counter; Sintaxis ES6 (opcional) Modificar estado Estado inicial
  • 26. Nuestro primer componente import React, { Component } from 'react'; class Counter extends Component { constructor(props) { super(props); this.state = {count: 1}; } tick() { this.setState({count: this.state.count + 1}); } render() { return ( <div className="App"> <button onClick={this.tick.bind(this)}>Clícame!</button> <span>Clicks: {this.state.count}</span> </div> ); } } export default Counter; Sintaxis ES6 (opcional) Modificar estado render(), lo llama React Estado inicial
  • 27. Trabajar con el estado
  • 28. Trabajar con el estado constructor(props) { super(props); this.state = {count: 1}; } Estado inicial
  • 29. Trabajar con el estado constructor(props) { super(props); this.state = {count: 1}; } Estado inicial this.setState({count: this.state.count + 1}); Asignar estado
  • 30. Trabajar con el estado constructor(props) { super(props); this.state = {count: 1}; } Estado inicial this.setState({count: this.state.count + 1}); Asignar estado this.state.count = this.state.count + 1; Simplemente recordar evitar
  • 31. render() y JSX render() { return ( <div className="App"> <button onClick={this.tick.bind(this)}>Clícame!</button> <span>Clicks: {this.state.count}</span> </div> ); } No es HTML, es JSX. React lo transforma internamente a elementos. Buena práctica: Dejar render() lo más limpio posible, solo un return.
  • 32. render() y JSX render() { return ( <div className="App"> <button onClick={this.tick.bind(this)}>Clícame!</button> <span>Clicks: {this.state.count}</span> </div> ); } No es HTML, es JSX. React lo transforma internamente a elementos. Algunas cosas cambian Buena práctica: Dejar render() lo más limpio posible, solo un return.
  • 33. render() y JSX render() { return ( <div className="App"> <button onClick={this.tick.bind(this)}>Clícame!</button> <span>Clicks: {this.state.count}</span> </div> ); } No es HTML, es JSX. React lo transforma internamente a elementos. Algunas cosas cambian Entre {} podemos insertar expresiones JS Buena práctica: Dejar render() lo más limpio posible, solo un return.
  • 34. Thinking in React render() { return ( <div className="App"> <button onClick={this.tick.bind(this)}>Clícame!</button> <span>Clicks: {this.state.count}</span> </div> ); }
  • 35. Thinking in React render() { return ( <div className="App"> <button onClick={this.tick.bind(this)}>Clícame!</button> <span>Clicks: {this.state.count}</span> </div> ); } Aquí no modificar el estado
  • 36. Thinking in React render() { return ( <div className="App"> <button onClick={this.tick.bind(this)}>Clícame!</button> <span>Clicks: {this.state.count}</span> </div> ); } Aquí no Ajax
  • 37. Thinking in React render() { return ( <div className="App"> <button onClick={this.tick.bind(this)}>Clícame!</button> <span>Clicks: {this.state.count}</span> </div> ); } Aquí no calcular decimales de pi y enviar un email
  • 38. Importante: pensar la jerarquía
  • 39. Importante: pensar la jerarquía
  • 40. Jerarquía de componentes: props class CounterGroup extends Component { render() { return ( <div> <Counter name="amigo"/> <Counter name="señor"/> </div> ); } }
  • 41. render() { return ( <div className="App"> <button onClick={this.tick.bind(this)}> Clícame {this.props.name} </button> <span>Clicks: {this.state.count}</span> </div> ); } y en Counter… Jerarquía de componentes: props class CounterGroup extends Component { render() { return ( <div> <Counter name="amigo"/> <Counter name="señor"/> </div> ); } }
  • 42. render() { return ( <div className="App"> <button onClick={this.tick.bind(this)}> Clícame {this.props.name} </button> <span>Clicks: {this.state.count}</span> </div> ); } y en Counter… Jerarquía de componentes: props class CounterGroup extends Component { render() { return ( <div> <Counter name="amigo"/> <Counter name="señor"/> </div> ); } }
  • 43. Pro tip: componentes sin estado const Saludador = (props) => { <div> <div>Hola {props.name}</div> </div> }
  • 44. Todo depende del estado, por tanto:
  • 45. Todo depende del estado, por tanto: •Podemos reproducir estados,
  • 46. Todo depende del estado, por tanto: •Podemos reproducir estados, •rebobinar,
  • 47. Todo depende del estado, por tanto: •Podemos reproducir estados, •rebobinar, •loguear cambios de estado,
  • 48. Todo depende del estado, por tanto: •Podemos reproducir estados, •rebobinar, •loguear cambios de estado, •hacer álbum de estilo
  • 49. Todo depende del estado, por tanto: •Podemos reproducir estados, •rebobinar, •loguear cambios de estado, •hacer álbum de estilo •…
  • 51. ¿Y si en lugar de algo así… render() { return ( <div className="App"> <button onClick={this.tick.bind(this)}>Clícame!</button> <span>Clicks: {this.state.count}</span> </div> ); }
  • 52. …tenemos algo así? render () { return ( <View> <ListView dataSource={dataSource} renderRow={(rowData) => <TouchableOpacity > <View> <Text>{rowData.name}</Text> <View> <SwitchIOS onValueChange={(value) => this.setMissing(item, value)} value={item.missing} /> </View> </View> </TouchableOpacity> } /> </View> ); }
  • 53. …tenemos algo así? render () { return ( <View> <ListView dataSource={dataSource} renderRow={(rowData) => <TouchableOpacity > <View> <Text>{rowData.name}</Text> <View> <SwitchIOS onValueChange={(value) => this.setMissing(item, value)} value={item.missing} /> </View> </View> </TouchableOpacity> } /> </View> ); } React Native
  • 54. React Targets •Web - react-dom •Mobile - React Native •Gl shaders - gl-react •Canvas - react-canvas •Terminal - react-blessed
  • 56. Setup
  • 60. Webpack • Gestiona dependencias por nosotros. Pros
  • 61. Webpack • Gestiona dependencias por nosotros. • Permite varios entornos: producción, desarrollo, …. Pros
  • 62. Webpack • Gestiona dependencias por nosotros. • Permite varios entornos: producción, desarrollo, …. • Recarga automática de página (e incluso hot reload). Pros
  • 63. Webpack • Gestiona dependencias por nosotros. • Permite varios entornos: producción, desarrollo, …. • Recarga automática de página (e incluso hot reload). • Uso de preprocesadores/“transpiladores”, como Babel. Pros
  • 64. Webpack • Gestiona dependencias por nosotros. • Permite varios entornos: producción, desarrollo, …. • Recarga automática de página (e incluso hot reload). • Uso de preprocesadores/“transpiladores”, como Babel. • Los programadores de frontend no nos arquearán la ceja. Pros
  • 65. Webpack • Gestiona dependencias por nosotros. • Permite varios entornos: producción, desarrollo, …. • Recarga automática de página (e incluso hot reload). • Uso de preprocesadores/“transpiladores”, como Babel. • Los programadores de frontend no nos arquearán la ceja. Pros Contras
  • 66. Webpack • Gestiona dependencias por nosotros. • Permite varios entornos: producción, desarrollo, …. • Recarga automática de página (e incluso hot reload). • Uso de preprocesadores/“transpiladores”, como Babel. • Los programadores de frontend no nos arquearán la ceja. Pros Contras • Tiene su curva de aprendizaje.
  • 67. Webpack • Gestiona dependencias por nosotros. • Permite varios entornos: producción, desarrollo, …. • Recarga automática de página (e incluso hot reload). • Uso de preprocesadores/“transpiladores”, como Babel. • Los programadores de frontend no nos arquearán la ceja. Pros Contras • Tiene su curva de aprendizaje. Ejemplo completo: https://github.com/Limenius/symfony-react-sandbox
  • 68. Inserción <div id="react-placeholder"></div> import ReactDOM from 'react-dom'; ReactDOM.render( <Counter name="amigo">, document.getElementById('react-placeholder') ); HTML JavaScript
  • 71. ReactBundle {{ react_component('RecipesApp', {'props': props}) }} import ReactOnRails from 'react-on-rails'; import RecipesApp from './RecipesAppServer'; ReactOnRails.register({ RecipesApp }); Twig: JavaScript:
  • 72. ReactBundle {{ react_component('RecipesApp', {'props': props}) }} import ReactOnRails from 'react-on-rails'; import RecipesApp from './RecipesAppServer'; ReactOnRails.register({ RecipesApp }); Twig: JavaScript:
  • 73. ReactBundle {{ react_component('RecipesApp', {'props': props}) }} import ReactOnRails from 'react-on-rails'; import RecipesApp from './RecipesAppServer'; ReactOnRails.register({ RecipesApp }); Twig: JavaScript:
  • 74. ReactBundle {{ react_component('RecipesApp', {'props': props}) }} import ReactOnRails from 'react-on-rails'; import RecipesApp from './RecipesAppServer'; ReactOnRails.register({ RecipesApp }); Twig: JavaScript: <div class="js-react-on-rails-component" style="display:none" data- component-name=“RecipesApp” data-props=“[mi Array en JSON]" data- trace=“false" data-dom-id=“sfreact-57d05640f2f1a”></div> HTML generado:
  • 77. Dame un estado y una función render() que dependa de él, y olvídate de cómo y cuándo pinto. La premisa fundamental
  • 78. Dame un estado y una función render() que dependa de él, y olvídate de cómo y cuándo pinto. La premisa fundamental Podemos renderizar componentes desde el servidor.
  • 79. Dame un estado y una función render() que dependa de él, y olvídate de cómo y cuándo pinto. La premisa fundamental Podemos renderizar componentes desde el servidor. • SEO friendly.
  • 80. Dame un estado y una función render() que dependa de él, y olvídate de cómo y cuándo pinto. La premisa fundamental Podemos renderizar componentes desde el servidor. • SEO friendly. • Carga de página más rápida.
  • 81. Dame un estado y una función render() que dependa de él, y olvídate de cómo y cuándo pinto. La premisa fundamental Podemos renderizar componentes desde el servidor. • SEO friendly. • Carga de página más rápida. • Podemos cachear.
  • 82. Client-side {{ react_component('RecipesApp', {'props': props, 'rendering': 'client-side'}}) }} TWIG
  • 83. Client-side {{ react_component('RecipesApp', {'props': props, 'rendering': 'client-side'}}) }} TWIG
  • 84. Client-side {{ react_component('RecipesApp', {'props': props, 'rendering': 'client-side'}}) }} TWIG <div class="js-react-on-rails-component" style="display:none" data- component-name=“RecipesApp” data-props=“…” data-dom- id=“sfreact-57d05640f2f1a”></div> HTML que devuelve el servidor
  • 85. Client-side {{ react_component('RecipesApp', {'props': props, 'rendering': 'client-side'}}) }} TWIG <div class="js-react-on-rails-component" style="display:none" data- component-name=“RecipesApp” data-props=“…” data-dom- id=“sfreact-57d05640f2f1a”></div> HTML que devuelve el servidor Generado en el navegador <div id="sfreact-57d05640f2f1a"><div data-reactroot="" data- reactid="1" data-react-checksum="2107256409"><ol class="breadcrumb" data-reactid="2"><li class="active" data-reactid=“3">Recipes</li> … … </div>
  • 86. Client-side y server-side {{ react_component('RecipesApp', {'props': props, 'rendering': 'both'}}) }} TWIG
  • 87. Client-side y server-side {{ react_component('RecipesApp', {'props': props, 'rendering': 'both'}}) }} TWIG
  • 88. Client-side y server-side {{ react_component('RecipesApp', {'props': props, 'rendering': 'both'}}) }} TWIG HTML que devuelve el servidor <div id="sfreact-57d05640f2f1a"><div data-reactroot="" data- reactid="1" data-react-checksum="2107256409"><ol class="breadcrumb" data-reactid="2"><li class="active" data-reactid=“3">Recipes</li> … … </div>
  • 89. Client-side y server-side {{ react_component('RecipesApp', {'props': props, 'rendering': 'both'}}) }} TWIG HTML que devuelve el servidor <div id="sfreact-57d05640f2f1a"><div data-reactroot="" data- reactid="1" data-react-checksum="2107256409"><ol class="breadcrumb" data-reactid="2"><li class="active" data-reactid=“3">Recipes</li> … … </div> Y React en el navegador toma el control al evaluar el código
  • 91. Opción 1: llamar a subproceso node.js Llamamos a node.js con el componente Process de Symfony * Cómodo (si tenemos node.js instalado). * Lento. Librería: https://github.com/nacmartin/phpexecjs
  • 92. Opción 2: v8js Usamos la extensión de PHP v8js * Cómodo (aunque puede que haya que compilar la extensión y v8). * Lento por ahora, potencialmente podríamos tener v8 precargada usando php-pm. Librería: https://github.com/nacmartin/phpexecjs
  • 93. Configuración Opciones 1 y 2 limenius_react: serverside_rendering: mode: "phpexecjs" phpexecjs detecta si tenemos la extensión v8js, y si no llama a node.js config.yml:
  • 94. Opción 3: Servidor externo Tenemos un servidor node.js “tonto” que nos renderiza React. Es un servidor de <100 líneas, que es independiente de nuestra lógica. * “Incómodo” (hay que mantener el servidor node.js corriendo, que tampoco es para morirse). * Rápido. Ver ejemplo en https://github.com/Limenius/symfony-react-sandbox
  • 95. Configuración Opción 3 limenius_react: serverside_rendering: mode: “external” server-socket-path: “../tal_y_tal/node.sock” config.yml:
  • 96. Lo mejor de los dos mundos En desarrollo usar llamada a node.js o v8js con phpexecjs. En producción tener un servidor externo. Si podemos cachear, menos problema.
  • 97. Es decir: limenius_react: serverside_rendering: mode: “external” server-socket-path: “../tal_y_tal/node.sock” config.yml: limenius_react: serverside_rendering: mode: "phpexecjs" config_dev.yml:
  • 98. ¿Vale la pena una app universal?
  • 99. ¿Vale la pena una app universal? En ocasiones sí, pero introduce complejidad.
  • 100. Soporte para Redux (+brevísima introducción a Redux)
  • 101. Redux: una cuestión de estado guardar Tu nombre: Juan Hola, Juan Cosas de Juan:
  • 102. Redux: una cuestión de estado guardar Tu nombre: Juan Hola, Juan Cosas de Juan:
  • 103. Redux: una cuestión de estado guardar Tu nombre: Juan Hola, Juan Cosas de Juan: state.name callback para cambiarlo
  • 105. dispatch(changeName(‘Juan')); Componente changeName = (name) => { return { type: ‘CHANGE_NAME', name } } Action
  • 106. dispatch(changeName(‘Juan')); Componente changeName = (name) => { return { type: ‘CHANGE_NAME', name } } Action const todo = (state = {name: null}, action) => { switch (action.type) { case 'CHANGE_USER': return { name: action.name } } } Reducer
  • 107. dispatch(changeName(‘Juan')); Componente changeName = (name) => { return { type: ‘CHANGE_NAME', name } } Action const todo = (state = {name: null}, action) => { switch (action.type) { case 'CHANGE_USER': return { name: action.name } } } Reducer Store
  • 108. this.props.name == ‘Juan';dispatch(changeName(‘Juan')); Componente changeName = (name) => { return { type: ‘CHANGE_NAME', name } } Action const todo = (state = {name: null}, action) => { switch (action.type) { case 'CHANGE_USER': return { name: action.name } } } Reducer Store
  • 109. Redux en ReactBundle Ver ejemplo en https://github.com/Limenius/symfony-react-sandbox import ReactOnRails from 'react-on-rails'; import RecipesApp from './RecipesAppClient'; import recipesStore from '../store/recipesStore'; ReactOnRails.registerStore({recipesStore}) ReactOnRails.register({ RecipesApp }); Twig: JavaScript: {{ redux_store('recipesStore', props) }} {{ react_component('RecipesApp') }}
  • 110. Redux en ReactBundle Ver ejemplo en https://github.com/Limenius/symfony-react-sandbox import ReactOnRails from 'react-on-rails'; import RecipesApp from './RecipesAppClient'; import recipesStore from '../store/recipesStore'; ReactOnRails.registerStore({recipesStore}) ReactOnRails.register({ RecipesApp }); Twig: JavaScript: {{ redux_store('recipesStore', props) }} {{ react_component('RecipesApp') }} {{ react_component('OtroComponente') }}
  • 111. Compartir store entre componentes
  • 112. Compartir store entre componentes React React React Twig Twig React Al compartir store comparten estado Twig
  • 113. Formularios, un caso especial
  • 114. Formularios muy dinámicos •Dentro de aplicaciones React. •Formularios importantísimos en los que un detalle de usabilidad vale mucho dinero. •Formularios muy específicos. •Formularios muy dinámicos que no cansen (ver typeform por ejemplo).
  • 117. Supongamos un form así public function buildForm(FormBuilderInterface $builder, array $options) { $builder ->add('country', ChoiceType::class, [ 'choices' => [ 'España' => 'es', 'Portugal' => 'pt', ] ]) ->add('addresses', CollectionType::class, ...); };
  • 120. Forms en HTML $form->createView(); submit país: España España Portugal direcciones: C tal- +state.usuario
  • 121. Forms en HTML $form->createView(); submit país: España España Portugal direcciones: C tal- +state.usuario
  • 122. Forms en HTML $form->createView(); submit país: España España Portugal direcciones: C tal- +state.usuario POST bien formadito con country:’es’ y no ‘España’, ‘espana', ‘spain', ‘0’…
  • 123. Forms en HTML $form->createView(); $form->submit($request); submit país: España España Portugal direcciones: C tal- +state.usuario POST bien formadito con country:’es’ y no ‘España’, ‘espana', ‘spain', ‘0’…
  • 125. Forms en API $form; submit país: España España Portugal direcciones: C tal- +state.usuario ✘
  • 126. Forms en API $form; submit país: España España Portugal direcciones: C tal- +state.usuario ✘ ¿Cómo sabemos los campos, o los choices? ¡A documentar! :(
  • 127. Forms en API $form; $form->submit($request); submit país: España España Portugal direcciones: C tal- +state.usuario POST “voy a tener suerte” ✘ ¿Cómo sabemos los campos, o los choices? ¡A documentar! :(
  • 128. Forms en API $form; $form->submit($request); submit país: España España Portugal direcciones: C tal- +state.usuario POST “voy a tener suerte” ✘ ¿Cómo sabemos los campos, o los choices? ¡A documentar! :( This form should not contain extra fields!!1
  • 129. Forms en API $form; $form->submit($request); submit país: España España Portugal direcciones: C tal- +state.usuario POST “voy a tener suerte” ✘ ¿Cómo sabemos los campos, o los choices? ¡A documentar! :( This form should not contain extra fields!!1 The value you selected is not a valid choice!!
  • 130. Forms en API $form; $form->submit($request); submit país: España España Portugal direcciones: C tal- +state.usuario POST “voy a tener suerte” ✘ ¿Cómo sabemos los campos, o los choices? ¡A documentar! :( This form should not contain extra fields!!1 The value you selected is not a valid choice!!One or more of the given values is invalid!! :D
  • 131. Forms en API $form; $form->submit($request); submit país: España España Portugal direcciones: C tal- +state.usuario POST “voy a tener suerte” ✘ ¿Cómo sabemos los campos, o los choices? ¡A documentar! :( This form should not contain extra fields!!1 The value you selected is not a valid choice!!One or more of the given values is invalid!! :DMUHAHAHAHAHA!!!!!
  • 134. Definir (y mantener) por triplicado Form SF API docs Form Cliente :( ¿Cuántos programadores hacen falta para hacer un formulario?
  • 139. Lo que necesitamos $form->createView(); HTML ¡Serializar! Vale, ¿A qué formato? API $miTransformador->transform($form);
  • 142. Qué pinta tiene { "$schema": "http://json-schema.org/draft-04/schema#", "title": "Product", "description": "A product from Acme's catalog", "type": "object", "properties": { "name": { "description": "Name of the product", "type": "string" }, "price": { "type": "number", "minimum": 0, "exclusiveMinimum": true }, "tags": { "type": "array", "items": { "type": "string" }, "minItems": 1, "uniqueItems": true } }, "required": ["id", "name", "price"] } definiciones, tipos, reglas de validación :) Nuevo recurso: mi-api/products/form
  • 143. A partir del schema generamos form
  • 144. Generadores client-side • jdorn/json-editor: no React, es un veterano. • mozilla/react-jsonschema-form: React. creado por un antiguo Symfonero (Niko Perriault). • limenius/liform-react: React + redux; integrado con redux-form (I ♥ redux-form) • … • Crear el nuestro puede ser conveniente.
  • 145. Generadores client-side: diferencias Cada uno amplía json-schema a su manera para especificar detalles UI: Orden de campos, qué widget específico usar, etc. Si queremos usarlos al máximo hay que generar un json-schema específico para cada uno (no son totalmente intercambiables). Ejemplo: usar editor Wysiwyg en un campo texto
  • 146. Ejemplo: LiformBundle y liform-react limenius/LiformBundle: Genera json-schema a partir de formularios Symfony. limenius/liform-react: Generador de formularios React (con redux-form) a partir de json-schema. Son Work in progress
  • 147. Cómo serializar: resolvers + transformers $transformer = $resolver->resolve($form); $jsonSchema = $transformer->transform($form); Ejemplo de esta técnica: https://github.com/Limenius/LiformBundle
  • 148. Resolver public function resolve(FormInterface $form) { $types = FormUtil::typeAncestry($form); foreach ($types as $type) { if (isset($this->transformers[$type])) { return $this->transformers[$type]; } } } Misión: Encuentra el transformer apropiado para el form
  • 149. Transformer Misión: Inspecciona el form y crea un array. Si es compuesto resuelve+transforma los hijos. class IntegerTransformer extends AbstractTransformer { public function transform(FormInterface $form) { $schema = [ 'type' => 'integer', ]; if ($liform = $form->getConfig()->getOption('liform')) { if ($format = $liform['format']) { $schema['format'] = $format; } } $this->addCommonSpecs($form, $schema); return $schema; } } protected function addLabel($form, &$schema) { if ($label = $form->getConfig()->getOption('label')) { $schema['title'] = $label; } }
  • 150. Transformer Recopila información de cada Form Field. Podemos sacar mucha información: •Valores por defecto & placeholders. •Atributos del formulario. •Validadores.
  • 151. Ejemplo: validación ‘pattern’ protected function addPattern($form, &$schema) { if ($attr = $form->getConfig()->getOption('attr')) { if (isset($attr['pattern'])) { $schema['pattern'] = $attr['pattern']; } } }
  • 152. Esta técnica vale también para Angular, Backbone, mobile…
  • 154. Repaso: • Qué es React • Setup • Apps universales (ReactBundle) • Para qué sirve Redux • El problema de los formularios • Cómo aliviarlo con JSON Schema
  • 155. MADRID · NOV 27-28 · 2015 ¡Gracias! @nacmartin nacho@limenius.com http://limenius.com Formación, consultoría y desarrollo de proyectos