Combo gagnant : React + Redux + RTK Query + Axios + authorization JWT
Internet ne sait pas tout !!
Sommaire
ToggleChou blanc
Pour les besoins d’un projet spécifique, j’ai fait un choix technologique qui m’a semblé évident sur le moment : associer la librairie React (ou le framework, selon vos convictions) avec les outils Redux RTK Query et la librairie Axios. Et le tout permettant de gérer une authentification JWT sur en tête http…
Je me suis bêtement dit : c’est un choix audacieux mais qui devrait être hyper pertinent. Ne perdons pas de temps à réinventer la roue et voyons ce qu’internet nous propose sur le sujet…
Et là… C’est le drame…
Impossible de trouver sur le net un exemple de ce montage particulier pour en extraire la substantifique moelle… C’est ballot.
Les mains dans le cambouis
Qu’à cela ne tienne. Je veux quand même aller au bout de mon idée. Ça n’est pas parce que je ne trouve aucune référence à cette association technologique que ça n’est pas une bonne idée.
Donc, je me retrousse les manches et je replonge dans les arcanes des documentations Redux, RTK Query et Axios afin de faire cohabiter tout ce petit monde ! Ça ne devrait pas être bien méchant !
Je vous livre ici ma solution, sachant que chaque développeur devra l’adapter à sa propre codebase.
Je zappe l’étape d’installation et paramétrage de React, vous trouverez des tonnes de ressources pour ça.
Commençons par un fichier de base de paramétrage de Axios.
J’ai choisi de le poser dans un répertoire ./src/app/apiHandler
import axios from 'axios'
const BASE_URL = 'http://<URL de votre API>'
export default axios.create({
baseURL: BASE_URL,
headers: { 'Content-Type': 'application/json' },
})
Ensuite, dans le même répertoire, j’ai créé le fichier apiSlice.js, qui comme son nom l’indique va regrouper les slice Redux de la partie api.
import { createApi } from '@reduxjs/toolkit/query/react'
import axios from './axios'
const axiosBaseQuery =
() =>
async ({ url, method, data, params, headers }) => {
try {
const result = await axios({
url: url,
method,
data,
params,
headers,
})
return { data: result.data }
} catch (axiosError) {
let err = axiosError
return {
error: {
status: err.response?.status,
data: err.response?.data || err,
},
}
}
}
export const apiSlice = createApi({
baseQuery: axiosBaseQuery(),
endpoints: (builder) => ({}),
})
Évidemment, puisque nous utilisons Redux, nous avons un store pour stocker les states.
Je l’ai mis dans ./src/app :
import { configureStore } from '@reduxjs/toolkit'
import { apiSlice } from './apiHandler/apiSlice'
import authSlice from '../features/auth/authSlice'
import userSlice from '../features/profile/userSlice'
export const store = configureStore({
reducer: {
[apiSlice.reducerPath]: apiSlice.reducer,
auth: authSlice,
user: userSlice,
},
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware().concat(apiSlice.middleware),
devTools: true,
})
export default store
Ok, on a une base de travail.
A présent, j’ai besoin d’actions et de reducer pour attaquer mon Api. C’est une API basique à lequelle on envoie l’email et le password dans le corps de la requête et qui retourne un token (le jeton Bearer). Évidemment, dans la vrai vie, on adaptera ce code pour sécuriser l’envoie du mot de passe et on stockera le token dans un cookie.
L’authentification
Pour faire simple et rapide, je ne vais m’attarder ici que sur la partie essentielle du code. Libre à vous de l’adapter à votre frontend. Ici, je fais du basique. On commence par les reducers dédiés à l’authentification :
import { createSlice } from '@reduxjs/toolkit'
const initialState = {
isLoading: false,
isAuth: false,
isRemember: true,
token: null,
error: '',
}
const authSlice = createSlice({
name: 'auth',
initialState,
reducers: {
logingPending: (state) => {
state.isLoading = true
},
logingSuccess: (state, action) => {
state.isLoading = false
state.isAuth = true
state.token = action.payload
state.error = ''
},
logingError: (state, action) => {
state.isLoading = false
state.error = action.payload
},
logingRemember: (state, action) => {
state.isRemember = action.payload
},
logingOut: (state) => {
state.isLoading = false
state.isAuth = false
state.token = null
},
},
})
const { actions, reducer } = authSlice
export const {
logingPending,
logingSuccess,
logingError,
logingOut,
logingRemember,
} = actions
export default reducer
export const selectCurrentIsAuth = (state) => state.auth.isAuth
export const selectCurrentToken = (state) => state.auth.token
Là, j’ai mes actions qui agiront sur les states et me permettront de stocker le token, notamment, lors de la phase d’authentification.
Ensuite, j’ai besoin d’un slice pour m’interfacer avec l’API et lui transmettre la requête :
import { apiSlice } from '../../app/apiHandler/apiSlice'
export const authApiSlice = apiSlice.injectEndpoints({
endpoints: (builder) => ({
login: builder.mutation({
query: (credentials) => ({
url: '/user/login',
method: 'post',
data: { ...credentials },
}),
}),
}),
})
export const { useLoginMutation } = authApiSlice
On y est presque. Pour simplifier, je ne vais pas détailler la page Login.jsx mais seulement les éléments qui nous intéressent : le formulaire de saisie des éléments d’authentification et le clic sur le bouton permettant de s’authentifier, après avoir renseigné l’email et le mot de passe. Et là, on voit que c’est super light ! Surtout le « handleClic ». C’était le but.
import { useState, useEffect } from 'react'
import { useNavigate } from 'react-router-dom'
import { useSelector, useDispatch } from 'react-redux'
import { useLoginMutation } from './authApiSlice'
import {
logingPending,
logingSuccess,
logingError
} from './authSlice'
const Login = () => {
const dispatch = useDispatch()
let navigate = useNavigate()
let content = ''
const [login] = useLoginMutation()
const { isAuth, isLoading, error, token } = useSelector(
(state) => state.auth
)
const [credentials, setCredientials] = useState({
email: '',
password: '',
})
// Clear error message if new user input
useEffect(() => {
dispatch(logingError(''))
}, [dispatch, credentials])
// Handles user input
const handleChange = ({ currentTarget }) => {
const { value, name } = currentTarget
if (name) {
setCredientials({
...credentials,
[name]: value,
})
}
}
const handleLogin = async (e) => {
e.preventDefault()
dispatch(logingPending())
try {
const userData = await login(credentials).unwrap()
dispatch(logingSuccess(userData.body.token))
navigate('/profile')
} catch (error) {
dispatch(logingError(error.data.message))
}
}
// Handles the standard profile page
content = isLoading ? (
<div className="temp-div ">
<h1>Loading...</h1>
</div>
) : (
<main className="main bg-dark">
<section className="sign-in-content">
<i className="fa fa-user-circle sign-in-icon"></i>
<h1>Sign In</h1>
{error && (
<div className="form-group">
<div className="alert alert-danger" role="alert">
{error}
</div>
</div>
)}
<form onSubmit={handleLogin}>
<div className="input-wrapper">
<label htmlFor="email">Email</label>
<input
type="email"
placeholder=""
id="email"
name="email"
onChange={handleChange}
autoComplete="false"
required
/>
</div>
<div className="input-wrapper">
<label htmlFor="password">Password</label>
<input
type="password"
placeholder="***********"
id="password"
name="password"
onChange={handleChange}
autoComplete="false"
required
/>
</div>
<button className="sign-in-button" disabled={isLoading}>
{isLoading && (
<span className="spinner-border spinner-border-sm">Loading</span>
)}
<span>Sign In</span>
</button>
</form>
</section>
</main>
)
return content
}
export default Login
Encore une fois, tout ceci est à adapter à votre codebase. Il va vous manquer le SCSS, et quelques composants React, comme le loader, par exemple… Mais vous avez saisi l’idée générale.
Conclusion
Finalement, ça n’était pas si compliqué ! Redux va gérer le middleware et les reducers. Les requêtes vers Axios seront générées par RTK Query. L’authentification se fait avec un jeton JWT qui est renvoyé par l’API lors du login et qui sera réutilisé pour les requêtes d’affichage du profil utilisateur, par exemple, ou pour gérer des habilitations à d’autres fonctionnalités de l’application. Idéalement, on donnera une durée de vie à ce token et on le stockera dans un cookie.
L’intérêt de ce montage ? Exploiter la puissance et la performance de Redux et RTK Query et la combiner à la puissance et à la souplesse de Axios, qui lui, nous permettra, entre autres, de gérer des interceptions (pour annuler une requête en cours, par exemple) de façon native.
#dev #reactjs #redux #rtkquery #axios
axialdata, c’est un cabinet de conseils en systèmes d’information qui vise à remettre l’humain au centre du processus de décision et du process informatique.
En facilitant les tâches automatisables et en simplifiant au maximum l’accès aux données de l’entreprise. Objectifs : rationaliser l’informatique, simplifier le quotidien, gagner de temps, rester serein…
axialdata apporte des solutions sur différents type de projets : gestion de site internet, SEO, référencement, développement, déploiement, gestion de projets, analyse de données, interconnexion de bases des données, automatisation, rationalisation, audit, conseil, sécurité, formation, accompagnement…
#axialdata #consultant #informatique #entreprise