s React.js a library or a framework? In any case, it is a new way of working that represents a revolution in the way of building web projects. It has very particular characteristics that allow us, for instance, to render React code from the server side, or to include React components from Twig tags. During this talk we will present React.js, we will explore how to take advantage of it from PHP projects and we will give answers to practical problems such as universal (isomorphical) rendering and the generation of React.js forms from Symfony forms without duplication of efforts.
2. My name is Nacho Martin.
Almost every project needs a rich frontend,
for one reason or another.
We build tailor-made projects.
So we have been thinking about this for some time.
I write code at Limenius
3. Why (as a PHP developer) should I
care about the frontend?
12. Options:
2: Find in the DOM where to
insert elements, what to move,
what to remove…
The fundamental premise
1: Re-render everything. Simple Not efficient
13. Options:
2: Find in the DOM where to
insert elements, what to move,
what to remove…
The fundamental premise
1: Re-render everything. Simple
Complex
Not efficient
14. Options:
2: Find in the DOM where to
insert elements, what to move,
what to remove…
The fundamental premise
1: Re-render everything. Simple
EfficientComplex
Not efficient
15. Options:
2: Find in the DOM where to
insert elements, what to move,
what to remove…
The fundamental premise
1: Re-render everything. Simple
EfficientComplex
Not efficient
React allows us to do 1, although it does 2 behind the scenes
16. Give me a state and a render() method that depends
on it and forget about how and when to render.*
The fundamental premise
17. Give me a state and a render() method that depends
on it and forget about how and when to render.*
The fundamental premise
* Unless you want more control, which is possible.
27. Working with state
constructor(props) {
super(props);
this.state = {count: 1};
}
Initial state
this.setState({count: this.state.count + 1});
Assign state
28. Working with state
constructor(props) {
super(props);
this.state = {count: 1};
}
Initial state
this.setState({count: this.state.count + 1});
Assign state
this.state.count = this.state.count + 1;
Just remember: avoid this
29. render() and JSX
render() {
return (
<div className="App">
<button onClick={this.tick.bind(this)}>Clícame!</button>
<span>Clicks: {this.state.count}</span>
</div>
);
It is not HTML, it is JSX.
React transforms it internally to HTML elements.
Good practice: make render() as clean as possible, only a return.
30. render() and JSX
render() {
return (
<div className="App">
<button onClick={this.tick.bind(this)}>Clícame!</button>
<span>Clicks: {this.state.count}</span>
</div>
);
It is not HTML, it is JSX.
React transforms it internally to HTML elements.
Some things change
Good practice: make render() as clean as possible, only a return.
31. render() and JSX
render() {
return (
<div className="App">
<button onClick={this.tick.bind(this)}>Clícame!</button>
<span>Clicks: {this.state.count}</span>
</div>
);
It is not HTML, it is JSX.
React transforms it internally to HTML elements.
Some things change
We can insert JS expressions between {}
Good practice: make render() as clean as possible, only a return.
33. Thinking in React
render() {
return (
<div className="App">
<button onClick={this.tick.bind(this)}>Click me!</button>
<span>Clicks: {this.state.count}</span>
</div>
);
}
Here we don’t modify state
34. Thinking in React
render() {
return (
<div className="App">
<button onClick={this.tick.bind(this)}>Click me!</button>
<span>Clicks: {this.state.count}</span>
</div>
);
}
Here we don’t make Ajax calls
35. Thinking in React
render() {
return (
<div className="App">
<button onClick={this.tick.bind(this)}>Click me!</button>
<span>Clicks: {this.state.count}</span>
</div>
);
}
Here we don’t calculate decimals of PI and send an e-mail
with the result
60. Webpack
• Manages dependencies
• Allows several environments: production, development, ….
• Automatic page reload (even hot reload).
Pros
61. Webpack
• Manages dependencies
• Allows several environments: production, development, ….
• Automatic page reload (even hot reload).
• Can use preprocessors/“transpilers”, like Babel.
Pros
62. Webpack
• Manages dependencies
• Allows several environments: production, development, ….
• Automatic page reload (even hot reload).
• Can use preprocessors/“transpilers”, like Babel.
Pros
Cons
63. Webpack
• Manages dependencies
• Allows several environments: production, development, ….
• Automatic page reload (even hot reload).
• Can use preprocessors/“transpilers”, like Babel.
Pros
Cons
• It has a non trivial learning curve.
64. Webpack
• Manages dependencies
• Allows several environments: production, development, ….
• Automatic page reload (even hot reload).
• Can use preprocessors/“transpilers”, like Babel.
Pros
Cons
• It has a non trivial learning curve.
I maintain a sandbox: https://github.com/Limenius/symfony-react-sandbox
73. Give me a state and a render() method that depends
on it and forget about how and when to render.
The fundamental premise
74. Give me a state and a render() method that depends
on it and forget about how and when to render.
The fundamental premise
We can render components in the server
75. Give me a state and a render() method that depends
on it and forget about how and when to render.
The fundamental premise
We can render components in the server
• SEO friendly.
76. Give me a state and a render() method that depends
on it and forget about how and when to render.
The fundamental premise
We can render components in the server
• SEO friendly.
• Faster perceived page loads.
77. Give me a state and a render() method that depends
on it and forget about how and when to render.
The fundamental premise
We can render components in the server
• SEO friendly.
• Faster perceived page loads.
• We can cache.
79. Client-side + Server-side
{{ react_component('RecipesApp', {'props': props, rendering': 'both'}}) }}
TWIG
HTML returned by the server
<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>
80. Client-side + Server-side
{{ react_component('RecipesApp', {'props': props, rendering': 'both'}}) }}
TWIG
HTML returned by the server
<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>
An then React in the browser takes control over the component
82. Option 1: Call a node.js subprocess
Make a call to node.js using Symfony Process component
* Easy (if we have node.js installed).
* Slow.
Library: https://github.com/nacmartin/phpexecjs
83. Option 2: v8js
Use PHP extension v8js
* Easy (although compiling the extension and v8 is not a breeze).
* Currently slow, maybe we could have v8 preloaded using php-pm so it
is not destroyed after every request-response cycle.
Library: https://github.com/nacmartin/phpexecjs
84. Option 3: External node.js server
We have “stupid” node.js server used only to render React components.
It has <100 LoC, and it doesn’t know anything about our logic.
* “Annoying” (we have to keep it running, which is not super annoying
either).
* Faster.
There is an example a dummy server for this purpose at
https://github.com/Limenius/symfony-react-sandbox
85. Options 1 & 2
$renderer = new PhpExecJsReactRenderer(‘path_to/server-bundle.js’);
$ext = new ReactRenderExtension($renderer, 'both');
$twig->addExtension($ext);
phpexecjs detects the presence of the extension v8js,
if not, calls node.js
86. Option 3
$renderer = new ExternalServerReactRenderer(‘../some_path/node.sock’);
$ext = new ReactRenderExtension($renderer, 'both');
$twig->addExtension($ext);
87. The best of the two worlds
In development use node.js or v8js with phpexecjs.
In production use an external server.
If we can cache server-side responses, even better.
106. Dynamic forms, why?
•Inside of React components.
•Important forms where UX means better conversions.
•Very specific forms.
•Very dynamic forms that aren’t boring (see Typeform for instance).
110. Typically PHP frameworks have a Form Component
$form (e.g. Form Symfony Component)
Initial values
111. Typically PHP frameworks have a Form Component
$form (e.g. Form Symfony Component)
Initial values
UI hints (widgets, attributes)
112. Typically PHP frameworks have a Form Component
$form (e.g. Form Symfony Component)
Initial values
UI hints (widgets, attributes)
Bind incoming data
113. Typically PHP frameworks have a Form Component
$form (e.g. Form Symfony Component)
Initial values
UI hints (widgets, attributes)
Bind incoming data
Deserialize
114. Typically PHP frameworks have a Form Component
$form (e.g. Form Symfony Component)
Initial values
UI hints (widgets, attributes)
Bind incoming data
Deserialize
Validate
115. Typically PHP frameworks have a Form Component
$form (e.g. Form Symfony Component)
Initial values
UI hints (widgets, attributes)
Bind incoming data
Deserialize
Validate
Return errors
116. Typically PHP frameworks have a Form Component
$form (e.g. Form Symfony Component) $form->createView() (helpers in other Fws not Sf)
Initial values
UI hints (widgets, attributes)
Bind incoming data
Deserialize
Validate
Return errors
117. Typically PHP frameworks have a Form Component
$form (e.g. Form Symfony Component) $form->createView() (helpers in other Fws not Sf)
Initial values
UI hints (widgets, attributes)
Bind incoming data
Deserialize
Validate
Return errors
Render view
118. Typically PHP frameworks have a Form Component
$form (e.g. Form Symfony Component) $form->createView() (helpers in other Fws not Sf)
Initial values
UI hints (widgets, attributes)
Bind incoming data
Deserialize
Validate
Return errors
Render view
Show errors after Submit
119. Typically PHP frameworks have a Form Component
$form (e.g. Form Symfony Component) $form->createView() (helpers in other Fws not Sf)
Initial values
UI hints (widgets, attributes)
Bind incoming data
Deserialize
Validate
Return errors
Render view
Some client-side validation (HTML5)
Show errors after Submit
120. Using forms in an API
$form $form->createView() (helpers in other Fws not Sf)
Initial values
UI hints (widgets, attributes)
Bind incoming data
Deserialize
Validate
Return errors
Render view
Some client-side validation (HTML5)
Show errors after Submit
121. …and we want more
$form $form->createView() (helpers in other Fws not Sf)
Initial values
UI hints (widgets, attributes)
Bind incoming data
Deserialize
Validate
Return errors
Render view
On Submit validation
Some client-side validation (HTML5)
122. …and we want more
$form $form->createView() (helpers in other Fws not Sf)
Initial values
UI hints (widgets, attributes)
Bind incoming data
Deserialize
Validate
Return errors
Render view
On blur sync validation
On Submit validation
Some client-side validation (HTML5)
123. …and we want more
$form $form->createView() (helpers in other Fws not Sf)
Initial values
UI hints (widgets, attributes)
Bind incoming data
Deserialize
Validate
Return errors
Render view
On blur sync validation
On Submit validation
On blur async validation
Some client-side validation (HTML5)
124. …and we want more
$form $form->createView() (helpers in other Fws not Sf)
Initial values
UI hints (widgets, attributes)
Bind incoming data
Deserialize
Validate
Return errors
Render view
On blur sync validation
On Submit validation
On blur async validation
Some client-side validation (HTML5)
All the dynamic goodies
125. Suppose this Symfony form
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('country', ChoiceType::class, [
'choices' => [
'United Kingdom' => 'gb',
'Deutschland' => 'de',
'España' => 'es',
]
])
->add('addresses', CollectionType::class, ...);
};
128. Forms rendered to HTML
$form->createView();
submit
Country: España
Deutschland
España
Addresses:
Some St.-
+state.usuario
129. Forms rendered to HTML
$form->createView();
submit
Country: España
Deutschland
España
Addresses:
Some St.-
+state.usuario
POST well formed
with country:’es’
and not ‘España’, ‘espana', ‘spain', ‘0’…
130. Forms rendered to HTML
$form->createView();
$form->submit($request);
submit
Country: España
Deutschland
España
Addresses:
Some St.-
+state.usuario
POST well formed
with country:’es’
and not ‘España’, ‘espana', ‘spain', ‘0’…
136. submit
Country: España
Deutschland
España
Addresses:
Some St.-
+state.usuario 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 in APIs
$form;
$form->submit($request);
POST “I'm Feeling Lucky”
✘
How do we know the visible choices or values?
Read the docs!
137. submit
Country: España
Deutschland
España
Addresses:
Some St.-
+state.usuario 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!!!!!
Forms in APIs
$form;
$form->submit($request);
POST “I'm Feeling Lucky”
✘
How do we know the visible choices or values?
Read the docs!
138.
139.
140. Define, maintain and keep in sync in triplicate
Form Server API docs Form Client
:_(
How many devs does it take to write a form?
141. Wizard type form?
“While you code this
we will be preparing
different versions for
other use cases”
156. Also important
•FormView serializer for initial values.
•Form serializer that extracts errors.
{ "code":null,
"message":"Validation Failed",
"errors":{
"children":{
"name":{
"errors":[
"This value should not be equal to Beetlejuice."
]
},
"description":[],
"dueTo":[]
}
}
}
157. So far we have:
$form $form->createView() (helpers in other Fws not Sf)
Initial values
UI hints (widgets, attributes)
Bind incoming data
Deserialize
Validate
Return errors
Render view
On blur sync validation
On Submit validation
On blur async validation
Some client-side validation (HTML5)
All the dynamic goodies
160. Leverage json-schema: Form generators
• mozilla/react-jsonschema-form: React.
• limenius/liform-react: React + Redux; integrated with redux-form
(we ♥ redux-form).
• …
• Creating our own generator is not so difficult (you typically only
need all the widgets, only a subset)
161. liform-react
By using redux-form we can:
• Have sane and powerful representation of state in Redux.
• Integrate on-blur validation, async validation & on Submit
validation.
• Define our own widgets/themes.
• Know from the beginning that it is flexible enough.
162. liform-react
import React from 'react'
import { createStore, combineReducers } from 'redux'
import { reducer as formReducer } from 'redux-form'
import { Provider } from 'react-redux'
import Liform from 'liform-react'
const MyForm = () => {
const reducer = combineReducers({ form: formReducer })
const store = createStore(reducer)
const schema = {
//…
}
}
return (
<Provider store={store}>
<Liform schema={schema} onSubmit={(v) => {console.log(v)}}/>
</Provider>
)
}