+ )
+}
+
+export default HelloWorld
+```
+---
+
+## La syntaxe JSX
+
+Une représentation proche du XML d'une arborescence de composants. Il ne faut pas faire d'amalgame, **ce n'est pas du HTML**.
+
+La définition des attributs propres au DOM se fait via le nom de la propriété Javascript, pas de l'attribut HTML. **Par exemple l'attribut`class` en HTML devient `className` en JSX**.
+
+Il en est de même pour la **définition des styles**.
+
+---
+
+## `props`
+
+Elle permettent de passer des données aux composants.
+
+```jsx
+// my-app/src/components/props-example.js
+
+import React from 'react'
+
+class PropsExample extends React.Component {
+
+ render() {
+ return (
+ { this.props.text }
+ )
+ }
+
+}
+
+export default PropsExample
+
+// Exemple d'utilisation
+
+//
+
+```
+---
+
+### `props.children`
+
+La propriété `children` permet de récupérer les composants 'enfants' qui pourraient être passés au composant lors de son rendu.
+
+```jsx
+// my-app/src/components/my-list.js
+
+import React from 'react'
+
+class MyList extends React.Component {
+
+ render() {
+ return (
+
+
+
+```
+
+---
+
+## `state`
+
+La propriété `state` d'un composant représente l'état (au sens données) de celui ci.
+
+```jsx
+// my-app/src/components/clock.js
+
+import React from 'react'
+
+export default class Clock extends React.Component {
+
+ constructor(props) {
+ super(props)
+ // Initialisation du "state" du composant
+ this.state = {
+ time: new Date()
+ }
+ // On appelle la méthode tick() du composant
+ // toutes les secondes
+ setInterval(this.tick.bind(this), 1000);
+ }
+
+ // Méthode de rendu du composant
+ render() {
+ return (
+
Time: { this.state.time.toString() }
+ )
+ }
+
+ // La méthode tick() met à jour le state du composant avec
+ // la date courante
+ tick() {
+ this.setState({ time: new Date() });
+ }
+
+}
+```
+---
+
+## Évènements du DOM
+
+Les évènements du DOM sont interceptés par le passage de "callbacks" sur les propriétés `on` des composants.
+
+```jsx
+// my-app/src/components/counter.js
+
+import React from 'react'
+
+export default class Counter extends React.Component {
+ constructor(props) {
+ super(props)
+ // Initialisation du "state" du composant
+ this.state = {
+ count: 0
+ }
+ // On "lie" les méthodes de la classe à l'instance
+ this.increment = this.increment.bind(this)
+ this.decrement = this.decrement.bind(this)
+ }
+ // Méthode de rendu du composant
+ render() {
+ return (
+
+ Count: { this.state.count }
+
+
+
+ )
+ }
+ // La méthode increment() incrémente la valeur du compteur de 1
+ increment() {
+ this.setState(prevState => ({ count: prevState.count+1 }))
+ }
+ // La méthode decrement() incrémente la valeur du compteur de 1
+ decrement() {
+ this.setState(prevState => ({ count: prevState.count-1 }))
+ }
+}
+```
+
+---
+
+## Gestion des styles
+
+Différentes méthodologies existes pour gérer les styles des composants.
+
+La méthode classique CSS (avec ou sans préprocesseur SCSS/SASS) est tout à fait viable.
+
+Les styles des composants sont également souvent déclarés en Javascript et utilisés via les `props` des composants.
+
+D'autres solutions plus avancées comme [styled-components](https://www.styled-components.com/) ou [CSS Modules](https://github.com/css-modules/css-modules) existent également. Elles mettent l'accent sur la modularité et la réutilisation des règles de style entre les composants.
+
+---
+
+### Déclaration des styles en Javascript
+
+```jsx
+// my-app/src/components/my-styled-component.styles.js
+
+export default {
+ self: {
+ backgroundColor: 'red',
+ border: '1px solid black',
+ padding: '10px'
+ },
+ content: {
+ backgroundColor: 'blue',
+ border: '1px dotted white'
+ }
+}
+```
+
+```jsx
+// my-app/src/components/my-styled-component.js
+
+import React from 'react'
+import styles from './my-styled-component.styles.js'
+
+export default class MyStyledComponent extends React.Component {
+
+ render() {
+ return (
+
+
+ Isnt it pretty ?
+
+
+ )
+ }
+
+}
+```
+---
+## Composition des styles en Javascript
+
+En utilisant l'opérateur de décomposition `...`, il est possible
+de composer les styles.
+
+```jsx
+// my-app/src/components/my-styled-component.js
+
+import React from 'react'
+import styles from './my-styled-component.styles.js'
+
+export default class MyStyledComponent extends React.Component {
+
+ render() {
+ return (
+
+
+ Isnt it pretty ?
+
+
+ )
+ }
+
+}
+
+// Exemple d'utilisation
+
+
+
+```
+---
+
+## Cycle de vie et méthodes
+
+https://reactjs.org/docs/react-component.html
+
+---
+
+### Séquence de montage
+
+1. `constructor(props)`
+2. `componentWillMount()`
+3. `render()`
+4. `componentDidMount()`
+
+---
+
+### Séquence de mise à jour
+
+1. `componentWillReceiveProps(nextProps)`
+2. `shouldComponentUpdate(nextProps, nextState)`
+3. `componentWillUpdate(nextProps, nextState)`
+4. `render()`
+5. `componentDidUpdate(prevProps, prevState)`
+
+---
+
+### Séquence de démontage
+
+1. `componentWillUnmount()`
+
+---
+
+### Gestion des erreurs
+
+`componentDidCatch(error, info)`
+
+Permet d'intercepter les erreurs qui pourraient être émises dans l'arbre des sous composants.
+
+---
+
+## Composants "contrôlés" (formulaires)
+
+```jsx
+// my-app/src/components/my-form.js
+import React from 'react'
+
+export default class MyForm extends React.Component {
+
+ constructor(props) {
+ super(props);
+ this.state = {name: ''};
+ this.handleChange = this.handleChange.bind(this);
+ this.handleSubmit = this.handleSubmit.bind(this);
+ }
+
+ handleChange(evt) {
+ this.setState({value: evt.target.value});
+ }
+
+ handleSubmit(evt) {
+ console.log(`Votre nom est ${this.state.name}`);
+ evt.preventDefault();
+ }
+
+ render() {
+ return (
+
+ );
+ }
+}
+```
+---
+
+## Intégrer React avec d'autres librairies
+
+https://reactjs.org/docs/integrating-with-other-libraries.html
+
+---
+
+## Tester ses composants
+
+https://facebook.github.io/jest/docs/en/tutorial-react.html
+
+### Installer Jest
+
+```bash
+npm install --save-dev jest react-test-renderer babel-jest
+```
+
+### Configurer le script NPM
+
+```js
+// package.json
+{
+ // ...
+ "scripts" : {
+ "test": "jest"
+ }
+ //...
+}
+```
+---
+
+## Créer un script de test
+
+```
+// my-app/src/components/my-styled-component.test.js
+
+/* globals test, expect */
+import MyStyledComponent from './my-styled-component'
+import renderer from 'react-test-renderer'
+
+test('Navbar snapshot', () => {
+ const component = renderer.create()
+
+ let tree = component.toJSON()
+
+ // Vérifier que le composant n'a pas changé depuis le dernier
+ // snapshot.
+ // Voir https://facebook.github.io/jest/docs/en/snapshot-testing.html
+ // pour plus d'informations
+ expect(tree).toMatchSnapshot()
+
+ // L'API expect() de Jest est disponible à l'adresse
+ // https://facebook.github.io/jest/docs/en/expect.html
+
+ // Il est possible d'effectuer des vérifications plus avancées
+ // grâce au projet Enzyme (vérification du DOM, etc)
+ // Voir http://airbnb.io/enzyme/ et
+ // https://facebook.github.io/jest/docs/en/tutorial-react.html#dom-testing
+
+})
+```
+
+---
+
+### Lancer les tests
+
+```bash
+npm test
+```
+
+---
+
+## Gestion des routes
+
+https://reacttraining.com/react-router/web/
+
+### Installation du module
+
+```bash
+npm install --save react-router react-router-dom
+```
+
+---
+
+### Déclaration basique des routes
+
+```
+// my-app/src/app.js
+
+import React from 'react'
+import { HashRouter } from 'react-router-dom' // ou BrowserRouter
+import { Route, Switch, Redirect } from 'react-router'
+import { Clock, MyStyledComponent, My404Page } from './components'
+
+export default class App extends React.Component {
+
+ render() {
+
+
+
+
+
+
+
+
+
+
+
+
+ }
+
+}
+```
+
+---
+
+## Liens de navigation
+
+```
+// my-app/src/components/my-linked-component.js
+
+import React from 'react'
+import { Link } from 'react-router'
+
+export default class App extends React.Component {
+ render() {
+ return (
+
+
+
+ )
+ }
+}
+```
+---
+
+## Paramètres de route
+
+```
+// my-app/src/app.js
+
+import React from 'react'
+import { HashRouter } from 'react-router-dom' // ou BrowserRouter
+import { Route } from 'react-router'
+import { MyRouteParameter } from './components'
+
+export default class App extends React.Component {
+ render() {
+
+
+
+ }
+}
+```
+```
+// my-app/src/components/my-route-parameter.js
+
+import React from 'react'
+import { HashRouter } from 'react-router-dom' // ou BrowserRouter
+import { Route } from 'react-router'
+import { MyRouteParameter } from './components'
+
+export default class App extends React.Component {
+ render() {
+ const { match } = this.props
+ Paramètre: { match.params.myParam }
+ }
+}
+```
+
+---
+
+### Sous routes
+
+```
+// my-app/src/app.js
+
+import React from 'react'
+import { HashRouter } from 'react-router-dom'
+import { Route, Switch } from 'react-router'
+import { ProductPage } from './components'
+
+export default class App extends React.Component {
+ render() {
+
+
+
+
+
+ }
+}
+```
+```
+// my-app/src/component/product.js
+
+import React from 'react'
+import { Route, Switch } from 'react-router'
+import ProductView from './product-view'
+import ProductEdit from './product-edit'
+
+export default class ProductPage extends React.Component {
+ render() {
+ const { match } = this.props
+ return (
+
+
+
+
+ )
+ }
+}
+```
+---
+
+## Et plus encore...
+
+- [Transitions](https://reacttraining.com/react-router/web/example/animated-transitions)
+- [Liens personnalisés](https://reacttraining.com/react-router/web/example/custom-link)
+- [Chemins récursifs...](https://reacttraining.com/react-router/web/example/recursive-paths)
+
+---
+
+## État de l'application
+
+---
+
+## Architecture Flux
+
+![center 100%](img/flux-simple-f8-diagram-with-client-action-1300w.png)
+
+---
+
+## Redux
+
+https://redux.js.org/
+
+---
+
+## Flot des données
+
+![center 100%](./img/redux_flow.png?1)
+
+---
+
+## `store`, `actions` et `reducers`
+
+### Générateurs d'actions
+
+```js
+// actions.js
+
+export const ADD_PRODUCT = 'ADD_PRODUCT'
+
+export function addProduct (name, price) {
+ return {type: ADD_PRODUCT, product: {name, price}}
+}
+
+export const REMOVE_PRODUCT = 'REMOVE_PRODUCT'
+
+export function removeProduct (name) {
+ return {type: REMOVE_PRODUCT, productName: name}
+}
+```
+
+---
+
+### "Reducer" racine
+
+```js
+// reducer.js
+
+export default const rootReducer = (state, action) => {
+
+ console.log(`Action: ${JSON.stringify(action)}`)
+
+ switch (action.type) {
+
+ case ADD_PRODUCT:
+ // L'action est de type ADD_PRODUCT
+ // On ajoute le produit dans la liste et
+ // on retourne un nouvel état modifié
+ return {
+ products: [...state.products, action.product]
+ }
+
+
+ case REMOVE_PRODUCT:
+ // L'action est de type REMOVE_PRODUCT
+ // On filtre la liste des produits et on
+ // retourne un nouvel état modifié
+ return {
+ products: state.products.filter(p => p.name !== action.productName)
+ }
+ }
+
+ // Si l'action n'est pas gérée, on retoure l'état
+ // sans le modifier
+ return state
+
+}
+```
+
+---
+
+## Création du `store`
+```
+// store.js
+
+import { createStore } from 'redux'
+import rootReducer from './reducer'
+
+export function configureStore(initialState = { products: [] }) {
+ return createStore(
+ rootReducer,
+ initialState
+ )
+}
+```
+---
+## Tester
+
+```
+// store.test.js
+
+/* globals test, expect, jest */
+import { addProduct, removeProduct } from './actions'
+import { configureStore } from './store'
+
+test('Ajout/suppression des produits', () => {
+ // On créait une instance de notre store
+ // avec le state par défaut
+ const store = configureStore()
+
+ // On créait un "faux" subscriber
+ // pour vérifier que l'état du store
+ // à bien été modifié le nombre de fois voulu
+ const subscriber = jest.fn()
+
+ // On attache notre faux subscriber
+ // au store
+ store.subscribe(subscriber)
+
+ // On "dispatch" nos actions
+ store.dispatch(addProduct('pomme', 5))
+ store.dispatch(addProduct('orange', 7))
+ store.dispatch(removeProduct('pomme'))
+
+ // On s'assure que notre subscriber a bien été
+ // appelé
+ expect(subscriber).toHaveBeenCalledTimes(3)
+
+ const state = store.getState()
+
+ // On s'assure que l'état du store correspond
+ // à ce qu'on attend
+ expect(state).toMatchObject({
+ products: [
+ {name: 'orange', price: 7}
+ ]
+ })
+
+})
+```
+
+---
+
+## Actions asynchrones
+
+https://github.com/gaearon/redux-thunk
+
+```bash
+npm install --save redux-thunk
+```
+---
+
+### Ajout du middleware au `store`
+
+```
+// store.js
+
+import { createStore, applyMiddleware } from 'redux'
+import thunk from 'redux-thunk'
+import rootReducer from './reducer'
+
+export function configureStore(initialState = { products: [] }) {
+ return createStore(
+ rootReducer,
+ initialState,
+ applyMiddleware(thunk) // On ajoute le middleware 'thunk'
+ )
+}
+```
+---
+
+## Générateur d'actions asynchrones
+
+```
+// actions.js
+
+export const SAVE_PRODUCT = 'SAVE_PRODUCT'
+export const SAVE_PRODUCT_SUCCESS = 'SAVE_PRODUCT_SUCCESS'
+export const SAVE_PRODUCT_FAILURE = 'SAVE_PRODUCT_FAILURE'
+
+export function saveProduct (product) {
+ return (dispatch, getState) => {
+
+ dispatch({type: SAVE_PRODUCT, product})
+
+ return fetch('http://my-api/products', {
+ method: 'POST',
+ body: JSON.stringify(product)
+ })
+ .then(res => res.json())
+ .then(res => dispatch({ type: SAVE_PRODUCT_SUCCESS, product: res.data }))
+ .catch(error => dispatch({ type: SAVE_PRODUCT_FAILURE, error }))
+ }
+}
+```
+
+---
+
+## Utilisation avec React
+
+### Installer les dépendances
+```bash
+npm install --save react-redux
+```
+
+---
+
+### Déclaration de l'usage du `store` dans l'application
+
+```
+// my-app/src/index.js
+
+import ReactDOM from 'react-dom'
+import { Provider } from 'react-redux'
+import App from './app'
+import { configureStore } from './store'
+
+const store = configureStore()
+
+ReactDOM.render(
+
+
+ ,
+ document.getElementById('app')
+)
+
+```
+---
+```
+// my-app/src/app.js
+
+import React from 'react'
+import { addProduct } from './actions'
+import { connect } from 'react-redux'
+
+class App extends React.Component {
+
+ componentDidMount() {
+ // Toutes les 2 secondes, on "dispatch" une nouvelle
+ // action qui va ajouter un produit dans le store
+ setInterval(() => {
+ this.props.dispatch(addProduct(`My product #${Date.now()}`, Math.random()*10|0))
+ }, 2000)
+ }
+
+ render() {
+ // On créait une liste à partir des éléments présents
+ // dans le tableaux de produits
+ const items = this.props.products.map((p, i) => {
+ return
{p.name}
+ })
+ return (
+
+ {items}
+
+ )
+ }
+
+}
+
+// Cette fonction permet de "sélectionner" les éléments
+// qui nous intéressent dans le state et de les exposer
+// en tant que "props" sur notre composant
+const mapStateToProps = state => {
+ return {
+ products: state.products
+ }
+}
+
+// On "emballe" notre composant dans un "higher order component" (ou H.O.C.)
+// ce qui nous permet de récupérer les données à partir du store
+export default connect(mapStateToProps)(App)
+```
+
+---
+
+## Mise en application
+
+---
+
+### Contexte
+
+Implémenter une application de type "catalogue" comprenant les fonctionnalités suivantes:
+
+- Créer/modifier/supprimer des fiches "produit"
+- Trouver des produits via un système de recherche multicritères
+- Un mécanisme d'authentification par mot de passe
+- Un système de rôles basiques:
+ - Un rôle "administrateur" pouvant effectuer toutes les actions
+ - Un rôle "éditeur" pouvant créer/modifier/supprimer des fiches existantes
+ - Un rôle "consultant" permettant de lire/rechercher des fiches existantes
+
+---
+
+### Contraintes
+
+Vous pouvez sélectionner n'importe quel type de produit à implémenter (album de musique, collection de pins, bestiaire de jeu de rôle...). Voici cependant les différents types d'attributs qui devront être présents sur une fiche produit:
+
+- Un attribut de type numérique
+- Un attribut de type "liste de mots clés"
+- Un attribut de type "texte libre"
+- Un attribut de type "fichier"
+
+La partie "backend" est laissée à votre libre choix.
+
+---
+
+# Licence
+
+## CC BY-NC-SA 3.0 FR
+
+[Creative Commons - Attribution - Pas d’Utilisation Commerciale - Partage dans les Mêmes Conditions 3.0 France](https://creativecommons.org/licenses/by-nc-sa/3.0/fr/)
\ No newline at end of file