Modification des noeuds HTML directement (souvent avec jQuery)
L'état vit dans le noeud DOM
const h1 = document.querySelector('h1');
const text = h1.textContent;
if (text === “Hello”) {
h1.textContent = “Hi”; // On modifie le DOM directement
}
import './my-header.js';
import './my-article.js';
import './my-footer.js';
class MyPage extends LitElement {
render() {
return html`
<my-header></my-header>
<my-article></my-article>
<my-footer></my-footer>
`;
}
}
<div id="root">
<!-- This element's contents will be replaced with your component. -->
</div>
ReactDOM.render(
React.createElement('h1', null, 'Hello World!'),
document.getElementById('root')
);
<div id="root">
<!-- This element's contents will be replaced with your component. -->
</div>
ReactDOM.render(
<h1>Hello, world!</h1>,
document.getElementById('root')
);
function tick() {
const element = (
<div>
<h1>Hello, world!</h1>
<h2>It is {new Date().toLocaleTimeString()}.</h2>
</div>
);
ReactDOM.render(element, document.getElementById('root'));
}
setInterval(tick, 1000);
Démarrer un projet React avec webpack
let
let foo = 'foo';
foo = 'bar';
console.log(foo); // ==> "bar"
const
const bar = 42;
bar = 32; // TypeError: Assignment to constant variable.
const array = ['a', 'b', 'c'];
array.push('d');
console.log(array); // => ['a', 'b', 'c', 'd']
const object = { item: 'a' };
object.item = 'b';
console.log(object); // => {item: 'b'}
function run() {
var foo = 'Foo';
let bar = 'Bar';
console.log(foo, bar); // Foo Bar
{
var moo = 'Mooo';
let baz = 'Bazz';
console.log(moo, baz); // Mooo Bazz
}
console.log(moo); // Mooo
console.log(baz); // ReferenceError
}
function add(a, b) {
return a + b;
}
Arrow functions
const add = (a, b) => {
return a + b;
};
const add = (a, b) => a + b;
const users = [
{ name: 'Clément', age: 30 },
{ name: 'Marie-Elisabeth', age: 28 },
{ name: 'Basile', age: 3 },
{ name: 'Bébé 2', age: 0 }
];
map
const ages = users.map((item) => item.age); // ==> [30,28,3,0]
filter
const enfants = users.filter((item) => item.age < 12);
/*
[
{ name: 'Basile', age: 3 },
{ name: 'Bébé 2', age: 0 }
]
*/
sort
const enfants = [...users].sort((itemA, itemB) => itemA.age - itemB.age);
/*
[
{ name: 'Bébé 2', age: 0 }
{ name: 'Basile', age: 3 },
{ name: 'Marie-Elisabeth', age: 28 },
{ name: 'Clément', age: 30 },
]
*/
⚠️ sort mute les variables !!!
reduce
const enfants = users.reduce((currentObject, item) => {
currentObject[item.name] = item.age; // muter l'objet est plus performant !
return currentObject;
}, {});
/*
{
'Clément' : 30,
'Marie-Elisabeth' : 28,
'Basile' : 3,
'Bébé 2' : 0
}
*/
const agePersonne = 30;
const ville = 'Lille, France';
const object = { age: agePersonne, ville }; // shorthand notation
const { age } = object; // déstructuration sur le nom de l'attribut
console.log(age); // ==> 30
const array = [30, 28, 2, 0];
const [age1, age2, age3, age4] = array;
// déstructuration sur l'index dans le tableau
console.log(age1, age2, age3, age4); // ==> 30 28 2 0
const object = {
age: 30,
adresse: { city: 'Lille', pays: 'France' }
};
const {
adresse: { pays }
} = object;
console.log(pays); // ==> France
const object = {
age: 30,
adresse: { city: 'Lille', pays: 'France' }
};
const {
adresse: { pays: maVariable }
} = object;
console.log(maVariable); // ==> France
const personne = { name: 'Clément', age: 30 };
const newPersonne = { ...personne, name: 'Vincent' };
console.log(newPersonne); // ==> {name: 'Vincent', age: 30}
const array1 = [0, 2, 4, 6, 8];
const array2 = [1, 3, 5, 7, 9];
const newArray = [...array1, ...array2];
console.log(newArray); // ==> [0, 2, 4, 6, 8, 1, 3, 5, 7, 9]
const fetchItems = () => {
return fetch('/users/42/items')
.then((resp) => resp.json())
.then((items) => items.map((item) => item.id))
.then((ids) => {
/* use the ids */
})
.catch((error) => console.log(error.toString()));
};
fetchItems().then((ids) => console.log(ids));
await ne peut être utilisé que dans une fonction async
fetchItems retourne une Promesse!
const fetchItems = async () => {
try {
const response = await fetch('/users/42/items');
const items = await response.json();
const itemIds = items.map((item) => item.id);
return itemIds;
} catch (error) {
console.log(error.toString());
}
};
await itemIdsPromise();
Tout est composant!
Suite
State
Formulaires
const UserFieldName = ({ visible }) => {
const [textField, setTextFieldValue] = useState('');
return (
<div>
<input
type="text"
value={textField}
onChange={(e) => {
setTextFieldValue(e.target.value);
}}
/>
</div>
);
};
Actions
const UserFieldName = ({ onAdd }) => {
const [textField, setTextFieldValue] = useState('');
return (
<div>
<input
type="text"
value={textField}
onChange={(e) => {
setTextFieldValue(e.target.value);
}}
/>
<button
onClick={() => {
onAdd(textField);
setTextFieldValue('');
}}
>
Ajouter
</button>
</div>
);
};
Actions
const UsersList = ({ visible, onAdd }) => {
const [users, setUsers] = useState([]);
return (
<div>
<UsersListDisplay users={users} />
<UserFieldName
onAdd={(newUser) => {
setUsers([...users, newUser]);
}}
/>
</div>
);
};
Hands On : Création d'une liste
Exemple fin de la liste
npx create-react-app mon-appli
useEffect
Applique des effets de bord
const [value, setValue] = useState(4);
useEffect(() => {
console.log(`New value is ${value}`);
}, [value]);
useEffect - cleanup
const [value, setValue] = useState(4);
useEffect(() => {
window.addEventListener('resize', () => {
console.log('Window resized');
});
return () => {
window.removeEventListener('resize');
// appliqué lors du démontage du componsant
};
}, []); ==> seulement au premier rendu !
useMemo
Memoise une valeur
const value = {};
const memoisedValue = useMemo(() => {
return heavyComputation(value);
}, [value]);
useCallback
Memoise un callback
const Component = () => {
const [value, setValue] = useState();
const deleteItem = useCallback(() => {
setValue(/*newValue*/);
}, []);
return <ChildComponent onClick={deleteItem} />;
};
La création d'objets et de fonctions coute "cher" et créee des nouveaux rendus car nouvelle instance à chaque rendu.
Permet d'éviter un rendu de ChildComponent
Change sur la référence est non pas sur la valeur
Attention aux objets et tableaux.
const Component = ({ value }) => {
if (value === null) {
return null;
}
const [stateValue, setStateValue] = useState();
};
^ Incorrect
const useWindowWidth = () => {
const [width, setWidth] = useState(0);
useEffect(() => {
var handler = () => {
setWidth(window.innerWidth);
};
window.addEventListener('resize', handler);
return () => {
window.removeEventListener('resize', handler);
// appliqué lors du démontage du componsant
};
}, []);
return { width };
};
const MyComponent = () => {
const { width } = useWindowWidth();
};
const MyContext = createContext();
const Root = () => {
return (
<MyContext.Provider value={{ myValue: 'Toto' }}>
<App />
</MyContext.Provider>
);
};
const SomeDeepNestedComponent = () => {
const { myValue } = useContext(MyContext);
};
React est opinion-free
React est très "Single Page App" (SPA)
On ne peut pas créer une appli moderne sur une route
Non.
Non, mais faut bidouiller
https://blog.logrocket.com/how-react-hooks-can-replace-react-router
index.js
import { BrowserRouter } from 'react-router-dom';
ReactDOM.render(
<React.StrictMode>
<BrowserRouter>
<App />
</BrowserRouter>
</React.StrictMode>,
document.getElementById('root')
);
App.js
import { Link } from 'react-router-dom';
const App = () => (
<div className="App">
<header className="App-header">
<h1>Ma super Liste</h1>
</header>
<nav>
<Link to="/list">Liste</Link>
<Link to="/form">Ajouter un utilisateur</Link>
</nav>
</div>
);
App.js
import { Link, Routes, Route } from 'react-router-dom';
const App = () => (
<div className="App">
<header />
<nav />
<main>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/list" element={<List />} />
<Route path="/form" element={<AddUserForm />} />
</Routes>
</main>
</div>
);
index.js
import { BrowserRouter } from 'react-router-dom';
ReactDOM.render(
<React.StrictMode>
<BrowserRouter>
<Routes>
<Route path="/" element={<Home />}>
<Route path="/users" element={<List />} />
<Route path="/form" element={<AddUserForm />} />
</Route>
</Routes>
</BrowserRouter>
</React.StrictMode>,
document.getElementById('root')
);
App.js
import { Link, Routes, Route } from 'react-router-dom';
const App = () => (
<div className="App">
<header />
<nav />
<main>
<Outlet />
</main>
</div>
);
list_recap.jsx
export const ListDisplay = ({ list, onDelete }) => {
return (
<div className="items-container">
{list.map(({ name, age, department }, index) => (
<Link to={`/users/${name}`} key={`list_item_${name}`}>
<!-- -->
</Link>
))}
</div>
);
};
Page blanche !
index.js
import { BrowserRouter } from 'react-router-dom';
ReactDOM.render(
<React.StrictMode>
<BrowserRouter>
<Routes>
<Route path="/" element={<Home />}>
<Route path="/users" element={<List />} />
<Route path="/form" element={<AddUserForm />} />
<Route path="*" element={<div>404!</div>} />
</Route>
</Routes>
</BrowserRouter>
</React.StrictMode>,
document.getElementById('root')
);
index.js
import { BrowserRouter } from 'react-router-dom';
ReactDOM.render(
<React.StrictMode>
<BrowserRouter>
<Routes>
<Route path="/" element={<Home />}>
<Route path="/users" element={<List />}>
<Route path=":id" element={<User_page />} />
</Route>
<Route path="/form" element={<AddUserForm />} />
<Route path="*" element={<div>404!</div>} />
</Route>
</Routes>
</BrowserRouter>
</React.StrictMode>,
document.getElementById('root')
);
Il faut ajouter <Outlet/>
dans list.jsx
import { useParams } from 'react-router';
export const User_page = () => {
const params = useParams();
return <div>{params.id}</div>;
};
Hands on
React router
...
Mais comment on partage les données entre les pages?
import jsonData from './list/data.json';
export let persons = [...jsonData];
export const addPerson = (newUser) => {
persons = [...persons, newUser];
};
export const deletePerson = (nameToDelete) => {
persons = persons.filter(({ name }) => name !== nameToDelete);
};
Ne faites pas ça !
Oui, mais...
App.js
const userStore = useReducer(userReducers, { users: rawUsers });
user_reducer.js
export const userReducers = (state, action) => {
switch (action.type) {
case 'ADD_USER': {
return {
...state,
users: [...state.users, action.newUser]
};
}
case 'DELETE_USER': {
return {
...state,
users: state.users.filter(({ name }) => name !== action.nameToDelete)
};
}
}
};
store.js
export const StoreContext = createContext({});
user_reducer.js
export const userReducers = (state, action) => {
switch (action.type) {
case 'ADD_USER': {
return {
users: [...state.users, action.newUser]
};
}
case 'DELETE_USER': {
return {
users: state.users.filter(({ name }) => name !== action.nameToDelete)
};
}
}
};
import { StoreContext } from './reducers/store';
import { userReducers } from './reducers/user_reducer';
import rawUsers from './data/users.json';
const Root = () => {
const userStore = useReducer(userReducers, { users: rawUsers });
return (
<React.StrictMode>
<StoreContext.Provider value={{ userStore }}>
<BrowserRouter>
<!-- -->
</BrowserRouter>
</StoreContext.Provider>
</React.StrictMode>
);
};
Retour à notre liste..
const {
userStore: [state]
} = useContext(StoreContext);
const { users: list } = state;
Retour à notre liste..
const {
userStore: [state,dispatch]
} = useContext(StoreContext);
<ListDisplay
list={displayList}
onDelete={(nameToDelete) =>
dispatch({ type: 'DELETE_USER', nameToDelete } )}
/>
Dans le formulaire...
export const AddPersonForm = () => {
const {
userStore: [state, dispatch]
} = useContext(StoreContext);
const onAdd = (newUser) => {
dispatch({
type: 'ADD_USER',
newUser
});
};
Dans le formulaire...
<button
onClick={() => {
onAdd({ name, age, department });
resetForm();
navigate('/users');
}}
>
Ajouter
</button>
Hands on :
Corrigé part 4
Récupérer de la donnée avec react
fetch
fetch(url).then(result => ...)
Le result contient plusieurs choses
Exemple
return fetch(`https://jsonplaceholder.typicode.com/users/${id}`).then((result) => {
if (result.ok) {
return result.json(); // caster en typescript éventuellement as Promise<VotreType>
}
throw new Error(`Failed to fetch, status: ${result.status}`);
});
Utilisation avec react
Dans un useEffect :
useEffect(() => {
return fetch(`https://jsonplaceholder.typicode.com/users/${params.id}`)
.then((result) => {
if (result.ok) {
return result.json(); // caster en typescript éventuellement as Promise<VotreType>
}
throw new Error(`Failed to fetch, status: ${result.status}`);
})
.then((user) => setUser(user)); // ou dispatch dans un reducer par ex
}, [params.id]);
Quelques conseils
Exemple : useUser(id)
export const useUser = (id: string | null) => {
const { dispatch } = useContext(UsersWriteContext);
const { usersState } = useContext(UsersReadContext);
const user = usersState.users[id ?? ''];
useEffect(() => {
if (id && !user) {
fetchUser(id).then((user) => dispatch({ type: 'ADD_USER', newUser: user }));
}
}, [id, user]);
return { user };
};