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

Redux, Context, Signals... Welcome To The State Management World

By Damien Chazoule

Redux, Context, Signals... Welcome To The State Management World

  • 166