bit.ly/3Ut8lgm


THE
MANAGEMENT
WORLD

WELCOME
TO
STATE
 


THE
MANAGEMENT
WORLD

WELCOME
TO
STATE
 

WHO AM I !?

Damien CHAZOULE

Developer

JavaScript

FullStack

dmnchzl@pm.me

www.dmnchzl.dev

LinkedIn

damien-chazoule

@dmn_chzl

dev.to

www.dev.to/dmnchzl

GitHub

www.github.com/dmnchzl

Medium

dmnchzl.medium.com

GitLab

www.gitlab.com/dmnchzl

WTF IS STATE MANAGEMENT !?

Données partagées entre plusieurs composants / niveaux de composants

Meilleure scalabilité / Mise à l'échelle de l'application

Pattern de développement / Flux de données

Réusabilité et meilleure maintenabilité

Paradigme de programmation réactive

Simplicité d'implémentation !== Simplicité d'utilisation

EXPLAIN LIKE I'M 5...

Component

State

State Management

EXPLAIN LIKE I'M 5...

State Management

S
T
O
R
E

Dispatch



STATE
MANAGEMENT
 



REDUX

 

HOW TO INSTALL ?

npm install react-redux redux redux-thunk
/
├── public/
├── src/
│   ├── store/
│   │   ├── pizzas/
│   │   │   ├── actions.js
│   │   │   └── reducer.js
│   │   └── index.js
│   ├── App.jsx
│   ├── index.css
│   ├── main.jsx
├── .prettierrc
├── index.html
├── package.json
├── README.md
└── vite.config.js 

HOW TO REDUCE...

export const setPizzas = pizzas => ({
  type: 'PIZZAS/SET_ALL',
  payload: pizzas
});

export const addPizza = pizza => ({
  type: 'PIZZAS/ADD_ONE',
  payload: pizza
});

export const upPizza = pizza => ({
  type: 'PIZZAS/SET_ONE',
  payload: pizza
});

export const delPizza = uid => ({
  type: 'PIZZAS/DEL_ONE',
  payload: uid
});

export const resetPizzas = (): { type: string } => ({
  type: 'PIZZAS/RESET_ALL'
});
actions.js
const initialState = [
  {
    uid: 'c4lz0n3',
    label: 'Calzone'
  }
];

export default function pizzasReducer(state = initialState, action) {
  switch (action.type) {
    case 'PIZZAS/SET_ALL':
      return action.payload;

    case 'PIZZAS/ADD_ONE':
      return [...state, action.payload];

    case 'PIZZAS/SET_ONE':
      return state.map(p => (p.uid === action.payload.uid ? action.payload : p));

    case 'PIZZAS/DEL_ONE':
      return state.filter(p => p.uid !== action.payload);

    case 'PIZZAS/RESET_ALL':
      return initialState;

    default:
      return state;
  }
}
reducer.js

HOW TO PROVIDE STORE...

import { combineReducers, applyMiddleware, createStore } from 'redux';
import thunk from 'redux-thunk';
import pizzasReducer from './pizzas/reducer';

const rootReducer = combineReducers({
  pizzas: pizzasReducer
});

export const configureStore = (defaultState = {}) => {
  const composeEnhancers = applyMiddleware(thunk);

  return createStore(rootReducer, defaultState, composeEnhancers);
};
store/index.js
import React from 'react';
import ReactDOM from 'react-dom/client';
import { Provider } from 'react-redux';
import App from './App';
import { configureStore } from './store';
import './index.css';

const store = configureStore();

const root = ReactDOM.createRoot(document.getElementById('root'));

root.render(
  <Provider store={store}>
    <App />
  </Provider>
);
main.jsx

HOW TO USE...

import { useDispatch, useSelector } from 'react-redux';
import { addPizza, delPizza } from './store/pizzas/actions';

export default function App() {
  const dispatch = useDispatch();
  const pizzas = useSelector(state => state.pizzas);
  const isEmpty = useSelector(state => state.pizzas.length === 0);

  const handleClick = () => dispatch(
    addPizza({
      uid: 'v3gg13',
      label: 'Veggie'
    })
  );

  return (
    <div className="app">
      <ul>
        {pizzas.map(pizza => <li key={pizza.uid}>{pizza.label}</li>)}
      </ul>

      <div className="button-group">
        <button onClick={handleClick}>Add Veggie</button>
        <button onClick={() => dispatch(delPizza('v3gg13'))}>Del Veggie</button>
      </div>
    </div>
  );
}
App.jsx



STATE
MANAGEMENT
 



CONTEXT

 

HOW TO INSTALL ?

/
├── public/
├── src/
│   ├── contexts/
│   │   └── AppContext.jsx
│   ├── models/
│   ├── App.jsx
│   ├── index.css
│   ├── main.jsx
├── .prettierrc
├── index.html
├── package.json
├── README.md
└── vite.config.js 

DO IT YOUSELF !

HOW TO PROVIDE STATE !?

const initialState = [
  {
    uid: 'c4lz0n3',
    label: 'Calzone'
  }
];

const pizzasReducer = (state = initialState, action) => {
  switch (action.type) {
    case 'PIZZAS/SET_ALL':
      return action.payload;

    case 'PIZZAS/ADD_ONE':
      return [...state, action.payload];

    case 'PIZZAS/SET_ONE':
      return state.map(p => (p.uid === action.payload.uid ? action.payload : p));

    case 'PIZZAS/DEL_ONE':
      return state.filter(p => p.uid !== action.payload);

    case 'PIZZAS/RESET_ALL':
      return initialState;

    default:
      return state;
  }
};
AppContext.js
import { createContext, useContext, useReducer } from 'react';

// ...

const AppContext = createContext(initialState);

export const useAppContext = () => useContext(AppContext);

export default function AppProvider({ children }) {
  const [state, dispatch] = useReducer(pizzasReducer, initialState);

  const setPizzas = pizzas => dispatch({ type: 'PIZZAS/SET_ALL', payload: pizzas });
  const addPizza = pizza => dispatch({ type: 'PIZZAS/ADD_ONE', payload: pizza });
  const upPizza = pizza => dispatch({ type: 'PIZZAS/SET_ONE', payload: pizza });
  const delPizza = uid => dispatch({ type: 'PIZZAS/DEL_ONE', payload: uid });
  const resetPizzas = () => dispatch({ type: 'PIZZAS/RESET_ALL' });
  
  const value = [state, { setPizzas, addPizza, upPizza, delPizza, resetPizzas }];

  return (
    <AppContext.Provider value={value}>
      {children}
    </AppContext.Provider>
  );
}
AppContext.jsx

HOW TO CONSUME STATE !?

import React from 'react';
import ReactDOM from 'react-dom/client';
import AppProvider from './contexts/AppProvider';
import App from './App';
import './index.css';

const root = ReactDOM.createRoot(document.getElementById('root'));

root.render(
  <AppProvider>
    <App />
  </AppProvider>
);
main.jsx
import { useState } from 'react';
import { useAppContext } from './contexts/AppContext';

export default function App() {
  const [pizzas, { addPizza, delPizza }] = useAppContext();

  const handleClick = () => addPizza({
    uid: 'v3gg13',
    label: 'Veggie'
  });

  return (
    <div className="app">
      <ul>
        {pizzas.map(pizza => <li key={pizza.uid}>{pizza.label}</li>)}
      </ul>

      <div className="button-group">
        <button onClick={handleClick}>Add Veggie</button>
        <button onClick={() => delPizza('v3gg13')}>Del Veggie</button>
      </div>
    </div>
  );
}
App.jsx

DIFF

Redux

Context API

Concept

Flux de données unidirectionnel

Higher-Order Component

Apprentissage

Complex

Simple

Scalabilité

++

++

Maintenabilité

+

+

Renders

-
(Immutability)

+

Debug

+++

+



STATE
MANAGEMENT
 



SIGNALS

 

POC: PROOF OF CONCEPT

import { useRef } from 'react';

function useSignal(initialValue) {
  const ref = useRef(initialValue);
  const get = () => ref.current;
  const set = () => {
    ref.current = typeof val === 'function' ? val(ref.current) : val;
  };
  
  return [get, set];
}

export default function App() {
  const [getCounter, setCounter] = useSignal(0);

  const increment = () => setCounter(getCounter() + 1);
  const decrement = () => setCounter(getCounter() - 1);
  
  return (
    <div className="app">
      {/* THIS IS NOT WORKING */}
      <span>Value: {getCounter()}</span>

      <div className="button-group">
        <button onClick={decrement}>-1</button>
        <button onClick={increment}>+1</button>
      </div>
    </div>
  );
}

HOW TO INSTALL ?

/
├── public/
├── src/
│   ├── App.jsx
│   ├── index.css
│   ├── main.jsx
│   ├── store.js
├── .prettierrc
├── index.html
├── package.json
├── README.md
└── vite.config.js 
npm install @preact/signals-react

HOW TO SIGNAL...

import { signal, computed } from '@preact/signals-react';

export const pizzas = signal([
  {
    uid: 'c4lz0n3',
    label: 'Calzone'
  }
]);

export const isEmpty = computed(() => pizzas.value.length === 0);

export const setPizzas = pizzas => (pizzas.value = pizzas);

export const addPizza = pizza => {
  pizzas.value = [...pizzas.value, pizza];
};

export const upPizza = pizza => {
  pizzas.value = pizzas.value.map(p => (p.uid === pizza.uid ? pizza : p));
};

export const delPizza = uid => {
  pizzas.value = pizzas.value.filter(p => p.uid !== uid);
};

export const resetPizzas = () => (pizzas.value = []);
store.js

HOW TO USE...

import { useState } from 'react';
import { addPizza, delPizza, pizzas } from './store';

export default function App() {
  const handleClick = () => addPizza({
    uid: 'v3gg13',
    label: 'Veggie'
  });

  return (
    <div className="app">
      <ul>
        {pizzas.value.map(pizza => <li key={pizza.uid}>{pizza.label}</li>)}
      </ul>

      <div className="button-group">
        <button onClick={handleClick}>Add Veggie</button>
        <button onClick={() => delPizza('v3gg13')}>Del Veggie</button>
      </div>
    </div>
  );
}
App.jsx

THE GLOBAL STATE STRUGGLE

CONTEXT CHAOS

EXPLAIN LIKE I'M 5...

Component

State

With Signals

Component

.value

SIGNALS TO THE FUTURE

🦖-REX !!!

Nécessite de la rigueur lors de la mise à l'échelle

⚠️ Attention à React Router (une issue en version 1.2.X)

KISS - Keep It Simple Stupid

Faire du "neuf", avec du "vieux" (KnockoutJS) :

observable (state)

computed (side effect)

pureComputed (derivated state)

Easy to test 👍

THANK YOU !

QUESTION(S) ?

SOURCES

reveal.js
React
Redux
Preact
Simple Icons
dev.to
dev.to
Angular
Solid
Made with Slides.com