Skip to content

gvergnaud/react-side-effects

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

9 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

title theme revealOptions
React Side Effects
solarized
transition
slide

React

Side Effects

Slides: https://gvergnaud.github.io/react-side-effects


👋

Gabriel Vergnaud

Héticien de la P2017

Frontend engineer 
[@gvergnaud](https://github.com/gvergnaud) on 
[@GabrielVergnaud](https://twitter.com/GabrielVergnaud) on 

Note:

  • Qui suis je ?
    • gabriel vergnaud
    • Heticien P2017
    • developer à Sketchfab.com (On recrute!)
    • gvergnaud on github
    • GabrielVergnaud on twitter

🗺

I. qu'est ce que les side effects

II. les effects en React

III. gérer les erreurs

IV. les side effects et le state reducer pattern


I

les side effects ?

mais qu'est-ce donc


Le code qui intéragit avec le monde extérieur


Mais extérieur à quoi ?

Au **scope** de la fonction


let imInTheUpperScope = true

function sideEffectFunction(params) {
  // tout ce qui est défini ici
  // fait partie du scope de la function `sideEffectFunction`

  const imPartOfTheFunctionScope = true

  // si je modifie quelque chose
  // du scope parent, alors je fais un side effect:

  imInTheUpperScope = false // /!\ Side effect!

  // Modifier les paramètres est également un side effect
  params.hello = '👋'
}

// l'ordre des définitions n'a pas d'importance
let imAlsoInTheUpperScope = true

{}

Le scope de la function est determiné par ce qui est défini à l'intérieur de ses brackets


Quelques exemples de side effects :

Modifier des variables du scope extérieur
Les requètes HTTP
L'accès à une base de donnée
Cookies, localStorage
Web APIs (DOM, WebAudio, WebGL...)
File system

Note:

- Qu'est ce que les side effects ?

  • Tout ce qui touche au monde extérieur
    • HTTP
    • accès à une base de donnée
    • Cookies, localStorage
    • web APIs
    • File System

Les side effect en react


// in native: side effect
const view = () => {
  document.body.innerHTML = `
    <div class="container">
      <p>Hello world!</p>
    </div>
  `
}
// in React: no side effect
const View = () => {
  return (
    <div className="container">
      <p>Hello world!</p>
    </div>
  )
}

React rend les intéractions avec le DOM pures et déclaratives

Note:

  • Le principe de react est justement de ne pas avoir à faire de side effects pour modifier le DOM. On ne mute plus directement les DOM nodes, mais à la place on donne une configuration qui représente ce à quoi le DOM doit resembler en fonction de ses props.

Oui mais...

Le web, ce n'est pas seulement intéragir avec le DOM


quelques side effects, du dévelopement frontend

Les network requests (HTTP ou WebSocket la plupart du temps)

L'url et l'History pour le routing

Le localStorage ou les cookies pour la persistence

Les event listener globaux (scroll, drag & drop, etc)

les autres threads (service workers / web workers)

la console (seulement pour le débugging a priori)

Note: Cependant, lorsque l'on fait du web, on gère d'autres types de side effects que ceux du DOM. - Des network requests (HTTP ou WebSocket la pluspart du temps) - le localStorage ou les cookies pour la persistence - L'url et l'History pour le routing - les autres threads (service workers / web workers) - parfois on a besoin d'ajouter des event listener à la main sur la window (scroll, drag & drop, etc) - la console (seulement pour le débugging a priori)


pour tout ça, on utilise useEffect


const App = () => {
  useEffect(() => {
    // effects
  })
}

On place le code relatif aux effects dans un callback Ce callback sera runné de manière asynchrone, une fois que react a updaté le DOM.


const App = () => {
  useEffect(() => {
    // i can use 'user' and 'isAdmin'
  }, [user, isAdmin])
}

Les dépendances sont données en deuxième paramètre À chaque fois que l'une de ses dépendances va changer, le callback sera re-exécuté Le tableau de dépendances est optionelle si il n'est pas fourni, le callback sera exécuté après chaque render

Note:

dependances explicites

  • useEffect est une API très intéressante car elle permet de penser ses side effects comme une conséquence d'un changement de data. Les dépendances sont explicite, et le code est runné à chaque fois qu'une de ses dépendance change.

const App = () => {
  useEffect(() => {
    window.addEventListener('scroll', handler)
    return () => window.removeEventListener('scroll', handler)
  }, [handler])
}

À l'intérieur du useEffect, je peux retourner une fonction de cleanup Elle sera exécutée si notre composant est retiré du DOM ou si les dépendances ne sont plus à jour

Note:

cleanup callback

  • Pour certains effets, on a besoin de pouvoir les annuler lors que leur dépendances ont changé, comme le event listeners par exemple. Pour ça on peut retourner un callback de cleanup dans notre fonction d'effet.

à vos claviers


<iframe src="https://codesandbox.io/embed/wonderful-hypatia-f3g60?fontsize=14&view=editor" title="exercice use effect" allow="geolocation; microphone; camera; midi; vr; accelerometer; gyroscope; payment; ambient-light-sensor; encrypted-media" style="width:100%; height:500px; border:0; border-radius: 4px; overflow:hidden;" sandbox="allow-modals allow-forms allow-popups allow-scripts allow-same-origin"></iframe>

Créer un custom hook


Il est possible de composer plusieurs hooks ensemble
pour créer un nouveau hook.


<iframe src="https://codesandbox.io/embed/exercice-use-effect-wz4pz?fontsize=14&view=editor" title="exercice custom hook" allow="geolocation; microphone; camera; midi; vr; accelerometer; gyroscope; payment; ambient-light-sensor; encrypted-media" style="width:100%; height:500px; border:0; border-radius: 4px; overflow:hidden;" sandbox="allow-modals allow-forms allow-popups allow-scripts allow-same-origin"></iframe>

Les custom hooks permettent d'encapsuler la complexité de notre code.


En résumé

useEffect est une manière de rendre déclaratif du code effectful et impératif.

Déclaratif car le code est dépendant de la data.

Note: en résumé, useEffect est manière d'abstraire du code impératif pour le rendre déclaratif, c'est à dire dépendant de la data.


La gestion des erreurs


Les side effects sont par nature imprevisibles.

une request HTTP peut échouer un acces au cookie peut échouer (environement sandboxé, iframes...) une manipulation de DOM peut échouer (plusieurs modifications incompatibles)

Note: Les side effects sont par nature imprevisibles.

  • une request HTTP peut échouer
  • un acces au cookie peut échouer (dans un environnement sandboxé comme une iframe)
  • une manipulation de dom peut échouer (en cas de modification incompatible par une autre partie de notre codebase ou par l'utilisateur lui même, ses extensions etc)

Il faut donc considérer les cas d'erreur dans le code.


Les promises


const p = new Promise((resolve, reject) => {
  // make some effect
  if (itSucceeds)
    resolve(someData)

  if (itFails)
    reject(someError)
})

2 branches : une pour le succès et une pour l'erreur

Note: Pour ça, en javascript, on a quelque chose de pratique: les promises.

  • permet de gérer le cas d'erreur

fetchUser() // returns a promise
  .then(user => fetchFriends(user)) // chains a second promise
  .then(friends => {}) // when all the side effects are done

on peut séquencer plusieurs effets

Note:

  • permet de composer et de sequencer plusieurs side effects différents

Donc, pourquoi ne pas créer un *hook* pour utiliser des promises dans nos components ?
<iframe src="https://codesandbox.io/embed/cocky-breeze-0dxfk?fontsize=14&view=editor" title="exercice use promise" allow="geolocation; microphone; camera; midi; vr; accelerometer; gyroscope; payment; ambient-light-sensor; encrypted-media" style="width:100%; height:500px; border:0; border-radius: 4px; overflow:hidden;" sandbox="allow-modals allow-forms allow-popups allow-scripts allow-same-origin"></iframe>

Note:

TUTO construire un promise based useSideEffect hook

const usePromise = (getPromise, deps) => {
  const [type, setType] = useState('pending')
  const [data, setData] = useState(null)
  const [error, setError] = useState(null)
  useEffect(() => {
    let isCancelled = false
    setType('pending')
    setData(null)
    setError(null)

    getPromise()
      .then(data => {
        if (isCancelled) return
        setData(data)
        setType('resolved')
      })
      .catch(err => {
        if (isCancelled) return
        setError(err)
        setType('rejected')
      })

    return () => {
      isCancelled = true
    }
  }, deps)

  return [type, data, error]
}

Side effect et state reducer pattern


On a un problème.

puisque notre reducer est une fonction pure, elle ne doit pas avoir de side effect.

Note: pottentiel gif: problème


Son type est trop restrictif

(state, action) => state


Mais pourquoi pas le changer ?

(state, action) => [state, callback]


C'est l'approche de plusieurs langages fonctionelles comme Elm ou Reason

Note: deux approches :

  1. Changer la signature de notre réducer:

au lieu de (state, action) => state on passe à (state, action) => [state, effectCallback]

^ l'approche de ELM et de Reason. ça pose quand même un problème: quand est ce que l'on run les side effects ? Quand le side effect est synchrone, on peut avoir envie de l'avoir executé avant de re-render.

// CODE Sandbox ?


<iframe src="https://codesandbox.io/embed/interesting-yonath-bfou0?fontsize=14&view=editor" title="exercice use effect reducer " allow="geolocation; microphone; camera; midi; vr; accelerometer; gyroscope; payment; ambient-light-sensor; encrypted-media" style="width:100%; height:500px; border:0; border-radius: 4px; overflow:hidden;" sandbox="allow-modals allow-forms allow-popups allow-scripts allow-same-origin"></iframe>

Deuxième solution :

Utiliser des middlewares


C'est l'approche de Redux

La plus courante en javascript


<iframe src="https://codesandbox.io/embed/quirky-bird-ux76p?fontsize=14&view=editor" title="exercice : effect middleware" allow="geolocation; microphone; camera; midi; vr; accelerometer; gyroscope; payment; ambient-light-sensor; encrypted-media" style="width:100%; height:500px; border:0; border-radius: 4px; overflow:hidden;" sandbox="allow-modals allow-forms allow-popups allow-scripts allow-same-origin"></iframe>

Note:

Commencer par expliquer ce qu'est un middleware

  • faire un logger middleware
  • simple thunk middleware
  • effect middleware avec promises

Ou exécuter des side effect dans le contexte d'un state reducer ?

  1. utiliser un middleware

le middleware prend une fonction qui retourne une promesse et la transform en actions:

const someEffectAction = {
  type: 'SOME_EFFECT_ACTION',
  async effect() {
    // do stuff
  }
}

dispatch(someEffectAction)

Le middleware intercepte l'action et va remplacer la function effect par de la data:

{
  type: 'SOME_EFFECT_ACTION',
  effect: {
    type: 'pending',
    error: null,
    data: null
  }
}

puis

{
  type: 'SOME_EFFECT_ACTION',
  effect: {
    type: 'resolved',
    error: null,
    data: {...} // data returned by the promise
  }
}

ou

{
  type: 'SOME_EFFECT_ACTION',
  effect: {
    type: 'rejected',
    error: Error{...},  // error thrown by the promise
    data: null
  }
}

C'est l'approche prise par plusieurs projets open sources, dont Hyper, le terminal de zeit.co

TUTO construire ce middleware


Quel sont les bénéfices de cette approche ?

Nos effets deviennent inspectables car ils sont représentés par des actions On peut implémenter des optimistic updates automatiques

Note:

Quels sont les bénéfices ?

optimistic updates for free

Puisque l'on peut clairement identifier les actions qui représentent des effets, on peut créer une logique d'error handling commune.

L'optimistic update est le fait de faire comme si l'effect avait déjà eu lieu avant que la requete ai fini pour donner l'impression à l'utilisateur que notre application est super rapide.

cependant, si l'action fail, il faut rollback les changements sur le state pour bien indiquer qu'il y a eu une erreur!

Puisque l'on sait différentier une action 'pending' d'une action 'rejected', on peut automatiser ce processus pour toutes nos actions. il suffit de :

  • stocker le diff du state au pending
  • en cas d'erreur, réappliquer le state précédent
  • en cas de succès, on peut enlever le diff que l'on avait stocké.

Tester les side effects

Les side effects sont la partie compliqué à tester. Comment simplifier le testing de notre app ?

les mocks & dependency injection

On peut encore améliorer notre middleware:

Au lieux d'importer la logique de nos side effect directement dans notre code, on peut se créer des services qui vont se charcher de faire les side effect pour nous, et les injecter via notre middleware.

Comme ça, si on est dans un contexte de testes automatisé, dans lequel on a pas envie de vraiment faire des requests, on peut injecter un mock de notre service.

// inside our code

const apiService = {
  getUser(id) {
    return fetch(`/users/${id}`).then(res => res.json())
  }
}

effectMiddleware(apiService)

// Inside an automated test
const apiMock = {
  getUser(id) {
    return new Promise(resolve => {
      resolve({ name: 'Gabriel', id })
    })
  }
}

effectMiddleware(apiMock)

Merci

de votre attention


Gabriel Vergnaud

Note:

<style> .flex { display: flex; align-items: center; justify-content: center; } .flex > *:not(:first-child) { margin-left: 10px } img.simple-image.simple-image { border:none; box-shadow:none; background: none; } .white.white.white { color: white; text-shadow: 0 2px 4px rgba(0,0,0, .5); } .lower.lower.lower { text-transform: none; } .reveal { font-size: 35px; } .reveal small { font-size: .7em; } .reveal pre { border-radius: 5px; box-shadow: 0px 8px 25px rgba(0,0,0,.25); } .reveal section img { border: none; box-shadow: 0 5px 15px rgba(0, 0, 0, 0.15) } .reveal pre code { padding: 30px; border-radius: 5px; font-weight: normal; } .reveal code { font-weight: bold; } </style>

About

Intro course about using side effects in a react application

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published