Créer une application Web avec React, Redux et Sanity.io

Publié: 2022-03-10
Résumé rapide ↬ Headless CMS est un moyen puissant et facile de gérer le contenu et d'accéder à l'API. Basé sur React, Sanity.io est un outil transparent pour une gestion de contenu flexible. Il peut être utilisé pour créer des applications simples à complexes à partir de zéro. Dans cet article, Ifeanyi explique comment créer une application de référencement simple avec Sanity.io et React. Les états globaux seront gérés avec Redux et l'application sera stylée avec des composants stylés.

L'évolution rapide des plateformes numériques a imposé de sérieuses limites aux CMS traditionnels comme Wordpress. Ces plateformes sont couplées, rigides et focalisées sur le projet plutôt que sur le produit. Heureusement, plusieurs CMS sans tête ont été développés pour relever ces défis et bien d'autres.

Contrairement aux CMS traditionnels, les CMS sans tête, qui peuvent être décrits comme des logiciels en tant que service (SaaS), peuvent être utilisés pour développer des sites Web, des applications mobiles, des écrans numériques et bien d'autres. Ils peuvent être utilisés sur des plates-formes illimitées. Si vous recherchez un CMS indépendant de la plate-forme, axé sur les développeurs et offrant une prise en charge multiplateforme, vous n'avez pas besoin de chercher plus loin que le CMS sans tête.

Un CMS sans tête est tout simplement un CMS sans tête. L'en head fait ici référence au frontend ou à la couche de présentation tandis que le body fait référence au backend ou au référentiel de contenu. Cela offre de nombreux avantages intéressants. Par exemple, cela permet au développeur de choisir n'importe quelle interface de son choix et vous pouvez également concevoir la couche de présentation comme vous le souhaitez.

Il existe de nombreux CMS sans tête, parmi les plus populaires, citons Strapi, Contentful, Contentstack, Sanity, Butter CMS, Prismic, Storyblok, Directus, etc. Ces CMS sans tête sont basés sur des API et ont leurs points forts individuels. Par exemple, les CMS comme Sanity, Strapi, Contentful et Storyblok sont gratuits pour les petits projets.

Ces CMS sans tête sont également basés sur différentes piles technologiques. Alors que Sanity.io est basé sur React.js, Storyblok est basé sur Vue.js. En tant que développeur React, c'est la principale raison pour laquelle je me suis rapidement intéressé à Sanity. Cependant, étant un CMS sans tête, chacune de ces plates-formes peut être connectée à n'importe quel frontend, que ce soit Angular, Vue ou React.

Chacun de ces CMS sans tête a des plans gratuits et payants qui représentent une hausse de prix significative. Bien que ces plans payants offrent plus de fonctionnalités, vous ne voudriez pas payer autant pour un projet de petite à moyenne taille. Sanity essaie de résoudre ce problème en introduisant des options de paiement à l'utilisation. Avec ces options, vous pourrez payer ce que vous utilisez et éviter la flambée des prix.

Une autre raison pour laquelle j'ai choisi Sanity.io est leur langage GROQ. Pour moi, Sanity sort du lot en proposant cet outil. Les requêtes d'objets graphiques-relationnels (GROQ) réduisent le temps de développement, vous aident à obtenir le contenu dont vous avez besoin sous la forme dont vous avez besoin et aident également le développeur à créer un document avec un nouveau modèle de contenu sans modifications de code.

De plus, les développeurs ne sont pas contraints au langage GROQ. Vous pouvez également utiliser GraphQL ou même les axios traditionnels et fetch dans votre application React pour interroger le backend. Comme la plupart des autres CMS sans tête, Sanity dispose d'une documentation complète contenant des conseils utiles pour s'appuyer sur la plate-forme.

Remarque : cet article nécessite une compréhension de base de React, Redux et CSS.

Plus après saut! Continuez à lire ci-dessous ↓

Premiers pas avec Sanity.io

Pour utiliser Sanity sur votre ordinateur, vous devez installer l'outil Sanity CLI. Bien que cela puisse être installé localement sur votre projet, il est préférable de l'installer globalement pour le rendre accessible à toutes les applications futures.

Pour ce faire, entrez les commandes suivantes dans votre terminal.

 npm install -g @sanity/cli

L'indicateur -g dans la commande ci-dessus active l'installation globale.

Ensuite, nous devons initialiser Sanity dans notre application. Bien que cela puisse être installé en tant que projet séparé, il est généralement préférable de l'installer dans votre application frontale (dans ce cas, React).

Dans son blog, Kapehe a expliqué en détail comment intégrer Sanity à React. Il sera utile de parcourir l'article avant de poursuivre ce didacticiel.

Entrez les commandes suivantes pour initialiser Sanity dans votre application React.

 sanity init

La commande sanity devient disponible lorsque nous avons installé l'outil CLI Sanity. Vous pouvez afficher une liste des commandes Sanity disponibles en tapant sanity ou sanity help dans votre terminal.

Lors de la configuration ou de l'initialisation de votre projet, vous devrez suivre les invites pour le personnaliser. Vous devrez également créer un ensemble de données et vous pourrez même choisir leur ensemble de données personnalisé rempli de données. Pour cette application de liste, nous utiliserons l'ensemble de données de films de science-fiction personnalisés de Sanity. Cela nous évitera de saisir les données nous-mêmes.

Pour afficher et modifier votre ensemble de données, cd au sous-répertoire Sanity de votre terminal et entrez sanity start . Cela fonctionne généralement sur https://localhost:3333/ . Vous devrez peut-être vous connecter pour accéder à l'interface (assurez-vous de vous connecter avec le même compte que celui que vous avez utilisé lors de l'initialisation du projet). Une capture d'écran de l'environnement est présentée ci-dessous.

Présentation du serveur Sanity
Un aperçu du serveur de santé mentale pour l'ensemble de données de films de science-fiction. ( Grand aperçu )

Sanity-React Communication bidirectionnelle

Sanity et React doivent communiquer entre eux pour une application entièrement fonctionnelle.

Réglage des origines CORS dans Sanity Manager

Nous allons d'abord connecter notre application React à Sanity. Pour ce faire, connectez-vous à https://manage.sanity.io/ et localisez CORS origins sous API Settings dans l'onglet Settings . Ici, vous devrez accrocher votre origine frontale au backend Sanity. Notre application React s'exécute sur https://localhost:3000/ par défaut, nous devons donc l'ajouter au CORS.

Ceci est illustré dans la figure ci-dessous.

Paramètres d'origine CORS
Définition de l'origine CORS dans Sanity.io Manager. ( Grand aperçu )

Connecter la santé mentale pour réagir

Sanity associe un project ID à chaque projet que vous créez. Cet ID est nécessaire lors de la connexion à votre application frontale. Vous pouvez trouver l'ID du projet dans votre Sanity Manager.

Le backend communique avec React à l'aide d'une bibliothèque connue sous le nom de sanity client . Vous devez installer cette bibliothèque dans votre projet Sanity en saisissant les commandes suivantes.

 npm install @sanity/client

Créez un fichier sanitySetup.js (le nom du fichier n'a pas d'importance), dans le dossier src de votre projet et entrez les codes React suivants pour établir une connexion entre Sanity et React.

 import sanityClient from "@sanity/client" export default sanityClient({ projectId: PROJECT_ID, dataset: DATASET_NAME, useCdn: true });

Nous avons transmis notre projectId , le dataset name de l'ensemble de données et un booléen useCdn à l'instance du client sanity importé de @sanity/client . Cela fonctionne comme par magie et connecte notre application au backend.

Maintenant que nous avons terminé la connexion bidirectionnelle, passons directement à la construction de notre projet.

Configuration et connexion de Redux à notre application

Nous aurons besoin de quelques dépendances pour travailler avec Redux dans notre application React. Ouvrez votre terminal dans votre environnement React et entrez les commandes bash suivantes.

 npm install redux react-redux redux-thunk

Redux est une bibliothèque globale de gestion d'état qui peut être utilisée avec la plupart des frameworks frontaux et des bibliothèques telles que React. Cependant, nous avons besoin d'un outil intermédiaire react-redux pour permettre la communication entre notre magasin Redux et notre application React. Le thunk Redux nous aidera à renvoyer une fonction au lieu d'un objet d'action de Redux.

Bien que nous puissions écrire l'intégralité du flux de travail Redux dans un seul fichier, il est souvent plus propre et préférable de séparer nos préoccupations. Pour cela, nous allons diviser notre flux de travail en trois fichiers à savoir, actions , les reducers , puis le store . Cependant, nous avons également besoin d'un fichier séparé pour stocker les action types , également appelés constants .

Configuration du magasin

Le magasin est le fichier le plus important de Redux. Il organise et regroupe les états et les envoie à notre application React.

Voici la configuration initiale de notre magasin Redux nécessaire pour connecter notre flux de travail Redux.

 import { createStore, applyMiddleware } from "redux"; import thunk from "redux-thunk"; import reducers from "./reducers/"; export default createStore( reducers, applyMiddleware(thunk) );

La fonction createStore dans ce fichier prend trois paramètres : le reducer (obligatoire), l'état initial et l'amplificateur (généralement un middleware, dans ce cas, un thunk fourni via applyMiddleware ). Nos réducteurs seront stockés dans un dossier reducers et nous les combinerons et les exporterons dans un fichier index.js dans le dossier reducers . C'est le fichier que nous avons importé dans le code ci-dessus. Nous reviendrons sur ce dossier plus tard.

Introduction au langage GROQ de Sanity

Sanity va encore plus loin dans l'interrogation des données JSON en introduisant GROQ. GROQ signifie Graph-Relational Object Queries. Selon Sanity.io, GROQ est un langage de requête déclaratif conçu pour interroger des collections de documents JSON en grande partie sans schéma.

Sanity fournit même le GROQ Playground pour aider les développeurs à se familiariser avec le langage. Cependant, pour accéder à l'aire de jeux, vous devez installer sanity vision . Exécutez sanity install @sanity/vision sur votre terminal pour l'installer.

GROQ a une syntaxe similaire à GraphQL mais elle est plus condensée et plus facile à lire. De plus, contrairement à GraphQL, GROQ peut être utilisé pour interroger des données JSON.

Par exemple, pour récupérer chaque élément de notre document de film, nous utiliserons la syntaxe GROQ suivante.

 *[_type == "movie"]

Cependant, si nous souhaitons récupérer uniquement les _ids et crewMembers dans notre document de film. Nous devons spécifier ces champs comme suit.

 `*[_type == 'movie']{ _id, crewMembers }

Ici, nous avons utilisé * pour indiquer à GROQ que nous voulons tous les documents de _type movie. _type est un attribut sous la collection de films. Nous pouvons également retourner le type comme nous l'avons fait pour _id et crewMembers comme suit :

 *[_type == 'movie']{ _id, _type, crewMembers }

Nous travaillerons davantage sur GROQ en l'implémentant dans nos actions Redux, mais vous pouvez consulter la documentation de Sanity.io pour GROQ pour en savoir plus à ce sujet. La feuille de triche de requête GROQ fournit de nombreux exemples pour vous aider à maîtriser le langage de requête.

Configuration des constantes

Nous avons besoin de constantes pour suivre les types d'action à chaque étape du flux de travail Redux. Les constantes aident à déterminer le type d'action envoyé à chaque instant. Par exemple, nous pouvons suivre le chargement de l'API, son chargement complet et le moment où une erreur se produit.

Nous n'avons pas nécessairement besoin de définir des constantes dans un fichier séparé, mais pour plus de simplicité et de clarté, c'est généralement la meilleure pratique dans Redux.

Par convention, les constantes en Javascript sont définies avec des majuscules. Nous suivrons les meilleures pratiques ici pour définir nos constantes. Voici un exemple de constante pour indiquer les demandes de récupération de film en mouvement.

 export const MOVIE_FETCH_REQUEST = "MOVIE_FETCH_REQUEST";

Ici, nous avons créé une constante MOVIE_FETCH_REQUEST qui dénote un type d'action de MOVIE_FETCH_REQUEST . Cela nous aide à appeler facilement ce type d'action sans utiliser de strings et à éviter les bogues. Nous avons également exporté la constante pour qu'elle soit disponible n'importe où dans notre projet.

De même, nous pouvons créer d'autres constantes pour récupérer les types d'action indiquant quand la requête réussit ou échoue. Un code complet pour le movieConstants.js est donné dans le code ci-dessous.

 export const MOVIE_FETCH_REQUEST = "MOVIE_FETCH_REQUEST"; export const MOVIE_FETCH_SUCCESS = "MOVIE_FETCH_SUCCESS"; export const MOVIE_FETCH_FAIL = "MOVIE_FETCH_FAIL"; export const MOVIES_FETCH_REQUEST = "MOVIES_FETCH_REQUEST"; export const MOVIES_FETCH_SUCCESS = "MOVIES_FETCH_SUCCESS"; export const MOVIES_FETCH_FAIL = "MOVIES_FETCH_FAIL"; export const MOVIES_FETCH_RESET = "MOVIES_FETCH_RESET"; export const MOVIES_REF_FETCH_REQUEST = "MOVIES_REF_FETCH_REQUEST"; export const MOVIES_REF_FETCH_SUCCESS = "MOVIES_REF_FETCH_SUCCESS"; export const MOVIES_REF_FETCH_FAIL = "MOVIES_REF_FETCH_FAIL"; export const MOVIES_SORT_REQUEST = "MOVIES_SORT_REQUEST"; export const MOVIES_SORT_SUCCESS = "MOVIES_SORT_SUCCESS"; export const MOVIES_SORT_FAIL = "MOVIES_SORT_FAIL"; export const MOVIES_MOST_POPULAR_REQUEST = "MOVIES_MOST_POPULAR_REQUEST"; export const MOVIES_MOST_POPULAR_SUCCESS = "MOVIES_MOST_POPULAR_SUCCESS"; export const MOVIES_MOST_POPULAR_FAIL = "MOVIES_MOST_POPULAR_FAIL";

Ici, nous avons défini plusieurs constantes pour récupérer un film ou une liste de films, trier et récupérer les films les plus populaires. Notez que nous définissons des constantes pour déterminer quand la requête est loading , successful et failed .

De même, notre fichier personConstants.js est donné ci-dessous :

 export const PERSONS_FETCH_REQUEST = "PERSONS_FETCH_REQUEST"; export const PERSONS_FETCH_SUCCESS = "PERSONS_FETCH_SUCCESS"; export const PERSONS_FETCH_FAIL = "PERSONS_FETCH_FAIL"; export const PERSON_FETCH_REQUEST = "PERSON_FETCH_REQUEST"; export const PERSON_FETCH_SUCCESS = "PERSON_FETCH_SUCCESS"; export const PERSON_FETCH_FAIL = "PERSON_FETCH_FAIL"; export const PERSONS_COUNT = "PERSONS_COUNT";

Comme le movieConstants.js , nous définissons une liste de constantes pour récupérer une personne ou des personnes. Nous avons également défini une constante pour compter les personnes. Les constantes suivent la convention décrite pour movieConstants.js et nous les avons également exportées pour qu'elles soient accessibles à d'autres parties de notre application.

Enfin, nous allons implémenter le mode clair et sombre dans l'application et nous avons donc un autre fichier de constantes globalConstants.js . Jetons un coup d'œil.

 export const SET_LIGHT_THEME = "SET_LIGHT_THEME"; export const SET_DARK_THEME = "SET_DARK_THEME";

Ici, nous définissons des constantes pour déterminer quand le mode clair ou sombre est envoyé. SET_LIGHT_THEME détermine quand l'utilisateur passe au thème clair et SET_DARK_THEME détermine quand le thème sombre est sélectionné. Nous avons également exporté nos constantes comme indiqué.

Configurer les actions

Par convention, nos actions sont stockées dans un dossier séparé. Les actions sont regroupées selon leur type. Par exemple, nos actions de film sont stockées dans movieActions.js tandis que nos actions de personne sont stockées dans le fichier personActions.js .

Nous avons également globalActions.js pour s'occuper de basculer le thème du mode clair au mode sombre.

Allons chercher tous les films dans moviesActions.js .

 import sanityAPI from "../../sanitySetup"; import { MOVIES_FETCH_FAIL, MOVIES_FETCH_REQUEST, MOVIES_FETCH_SUCCESS } from "../constants/movieConstants"; const fetchAllMovies = () => async (dispatch) => { try { dispatch({ type: MOVIES_FETCH_REQUEST }); const data = await sanityAPI.fetch( `*[_type == 'movie']{ _id, "poster": poster.asset->url, } ` ); dispatch({ type: MOVIES_FETCH_SUCCESS, payload: data }); } catch (error) { dispatch({ type: MOVIES_FETCH_FAIL, payload: error.message }); } };

Vous vous souvenez quand nous avons créé le fichier sanitySetup.js pour connecter React à notre backend Sanity ? Ici, nous avons importé la configuration pour nous permettre d'interroger notre backend sain d'esprit à l'aide de GROQ. Nous avons également importé quelques constantes exportées du fichier movieConstants.js dans le dossier des constants .

Ensuite, nous avons créé la fonction d'action fetchAllMovies pour récupérer tous les films de notre collection. La plupart des applications React traditionnelles utilisent axios ou fetch pour récupérer les données du backend. Mais bien que nous puissions utiliser n'importe lequel d'entre eux ici, nous utilisons le GROQ de Sanity. Pour entrer en mode GROQ , nous devons appeler la fonction sanityAPI.fetch() comme indiqué dans le code ci-dessus. Ici, sanityAPI est la connexion React-Sanity que nous avons configurée précédemment. Cela renvoie une Promise et doit donc être appelé de manière asynchrone. Nous avons utilisé la syntaxe async-await ici, mais nous pouvons également utiliser la syntaxe .then .

Puisque nous utilisons thunk dans notre application, nous pouvons renvoyer une fonction au lieu d'un objet d'action. Cependant, nous avons choisi de transmettre l'instruction de retour sur une seule ligne.

 const fetchAllMovies = () => async (dispatch) => { ... }

Notez que nous pouvons également écrire la fonction de cette façon :

 const fetchAllMovies = () => { return async (dispatch)=>{ ... } }

En général, pour récupérer tous les films, nous avons d'abord envoyé un type d'action qui suit le moment où la requête est toujours en cours de chargement. Nous avons ensuite utilisé la syntaxe GROQ de Sanity pour interroger de manière asynchrone le document du film. Nous avons récupéré le _id et l'url de l'affiche des données du film. Nous avons ensuite renvoyé une charge utile contenant les données obtenues à partir de l'API.

De même, nous pouvons récupérer des films par leur _id , trier des films et obtenir les films les plus populaires.

Nous pouvons également récupérer des films qui correspondent à la référence d'une personne en particulier. Nous l'avons fait dans la fonction fetchMoviesByRef .

 const fetchMoviesByRef = (ref) => async (dispatch) => { try { dispatch({ type: MOVIES_REF_FETCH_REQUEST }); const data = await sanityAPI.fetch( `*[_type == 'movie' && (castMembers[person._ref match '${ref}'] || crewMembers[person._ref match '${ref}']) ]{ _id, "poster" : poster.asset->url, title } ` ); dispatch({ type: MOVIES_REF_FETCH_SUCCESS, payload: data }); } catch (error) { dispatch({ type: MOVIES_REF_FETCH_FAIL, payload: error.message }); } };

Cette fonction prend un argument et vérifie si person._ref dans castMembers ou crewMembers correspond à l'argument passé. Nous renvoyons le film _id , poster url et title à côté. Nous envoyons également une action de type MOVIES_REF_FETCH_SUCCESS , en attachant une charge utile des données renvoyées, et si une erreur se produit, nous envoyons une action de type MOVIE_REF_FETCH_FAIL en attachant une charge utile du message d'erreur, grâce au wrapper try-catch .

Dans la fonction fetchMovieById , nous avons utilisé GROQ pour récupérer un film qui correspond à un id particulier passé à la fonction.

La syntaxe GROQ de la fonction est illustrée ci-dessous.

 const data = await sanityAPI.fetch( `*[_type == 'movie' && _id == '${id}']{ _id, "cast" : castMembers[]{ "ref": person._ref, characterName, "name": person->name, "image": person->image.asset->url } , "crew" : crewMembers[]{ "ref": person._ref, department, job, "name": person->name, "image": person->image.asset->url } , "overview": { "text": overview[0].children[0].text }, popularity, "poster" : poster.asset->url, releaseDate, title }[0]` );

Comme pour l'action fetchAllMovies , nous avons commencé par sélectionner tous les documents de type movie mais nous sommes allés plus loin pour ne sélectionner que ceux dont l'identifiant était fourni à la fonction. Puisque nous avons l'intention d'afficher beaucoup de détails pour le film, nous avons spécifié un tas d'attributs à récupérer.

Nous avons récupéré l' id du film ainsi que quelques attributs dans le tableau castMembers , à savoir ref , characterName , le nom de la personne et l'image de la personne. Nous avons également changé l'alias de castMembers en cast .

Comme les castMembers , nous avons sélectionné quelques attributs du tableau crewMembers , à savoir ref , department , job , le nom de la personne et l'image de la personne. nous avons également changé l'alias de crewMembers en crew .

De la même manière, nous avons sélectionné le texte de présentation, la popularité, l'URL de l'affiche du film, la date de sortie et le titre du film.

Le langage GROQ de Sanity nous permet également de trier un document. Pour trier un article, nous passons la commande à côté d'un opérateur pipe .

Par exemple, si nous souhaitons trier les films par releaseDate dans l'ordre croissant, nous pouvons procéder comme suit.

 const data = await sanityAPI.fetch( `*[_type == 'movie']{ ... } | order(releaseDate, asc)` );

Nous avons utilisé cette notion dans la fonction sortMoviesBy pour trier par ordre croissant ou décroissant.

Jetons un coup d'œil à cette fonction ci-dessous.

 const sortMoviesBy = (item, type) => async (dispatch) => { try { dispatch({ type: MOVIES_SORT_REQUEST }); const data = await sanityAPI.fetch( `*[_type == 'movie']{ _id, "poster" : poster.asset->url, title } | order( ${item} ${type})` ); dispatch({ type: MOVIES_SORT_SUCCESS, payload: data }); } catch (error) { dispatch({ type: MOVIES_SORT_FAIL, payload: error.message }); } };

Nous avons commencé par envoyer une action de type MOVIES_SORT_REQUEST pour déterminer quand la requête est en cours de chargement. Nous avons ensuite utilisé la syntaxe GROQ pour trier et récupérer les données de la collection de movie . La rubrique à trier est fournie dans la variable item et le mode de tri (ascendant ou décroissant) est fourni dans la variable type . Par conséquent, nous avons renvoyé l id , l'url de l'affiche et le titre. Une fois les données retournées, nous dispatchons une action de type MOVIES_SORT_SUCCESS et en cas d'échec, nous dispatchons une action de type MOVIES_SORT_FAIL .

Un concept GROQ similaire s'applique à la fonction getMostPopular . La syntaxe GROQ est illustrée ci-dessous.

 const data = await sanityAPI.fetch( ` *[_type == 'movie']{ _id, "overview": { "text": overview[0].children[0].text }, "poster" : poster.asset->url, title }| order(popularity desc) [0..2]` );

La seule différence ici est que nous avons trié les films par popularité par ordre décroissant, puis sélectionné uniquement les trois premiers. Les éléments sont retournés dans un index de base zéro et donc les trois premiers éléments sont les éléments 0, 1 et 2. Si nous souhaitons récupérer les dix premiers éléments, nous pourrions passer [0..9] à la fonction.

Voici le code complet des actions de film dans le fichier movieActions.js .

 import sanityAPI from "../../sanitySetup"; import { MOVIE_FETCH_FAIL, MOVIE_FETCH_REQUEST, MOVIE_FETCH_SUCCESS, MOVIES_FETCH_FAIL, MOVIES_FETCH_REQUEST, MOVIES_FETCH_SUCCESS, MOVIES_SORT_REQUEST, MOVIES_SORT_SUCCESS, MOVIES_SORT_FAIL, MOVIES_MOST_POPULAR_REQUEST, MOVIES_MOST_POPULAR_SUCCESS, MOVIES_MOST_POPULAR_FAIL, MOVIES_REF_FETCH_SUCCESS, MOVIES_REF_FETCH_FAIL, MOVIES_REF_FETCH_REQUEST } from "../constants/movieConstants"; const fetchAllMovies = () => async (dispatch) => { try { dispatch({ type: MOVIES_FETCH_REQUEST }); const data = await sanityAPI.fetch( `*[_type == 'movie']{ _id, "poster" : poster.asset->url, } ` ); dispatch({ type: MOVIES_FETCH_SUCCESS, payload: data }); } catch (error) { dispatch({ type: MOVIES_FETCH_FAIL, payload: error.message }); } }; const fetchMoviesByRef = (ref) => async (dispatch) => { try { dispatch({ type: MOVIES_REF_FETCH_REQUEST }); const data = await sanityAPI.fetch( `*[_type == 'movie' && (castMembers[person._ref match '${ref}'] || crewMembers[person._ref match '${ref}']) ]{ _id, "poster" : poster.asset->url, title }` ); dispatch({ type: MOVIES_REF_FETCH_SUCCESS, payload: data }); } catch (error) { dispatch({ type: MOVIES_REF_FETCH_FAIL, payload: error.message }); } }; const fetchMovieById = (id) => async (dispatch) => { try { dispatch({ type: MOVIE_FETCH_REQUEST }); const data = await sanityAPI.fetch( `*[_type == 'movie' && _id == '${id}']{ _id, "cast" : castMembers[]{ "ref": person._ref, characterName, "name": person->name, "image": person->image.asset->url } , "crew" : crewMembers[]{ "ref": person._ref, department, job, "name": person->name, "image": person->image.asset->url } , "overview": { "text": overview[0].children[0].text }, popularity, "poster" : poster.asset->url, releaseDate, title }[0]` ); dispatch({ type: MOVIE_FETCH_SUCCESS, payload: data }); } catch (error) { dispatch({ type: MOVIE_FETCH_FAIL, payload: error.message }); } }; const sortMoviesBy = (item, type) => async (dispatch) => { try { dispatch({ type: MOVIES_MOST_POPULAR_REQUEST }); const data = await sanityAPI.fetch( `*[_type == 'movie']{ _id, "poster" : poster.asset->url, title } | order( ${item} ${type})` ); dispatch({ type: MOVIES_SORT_SUCCESS, payload: data }); } catch (error) { dispatch({ type: MOVIES_SORT_FAIL, payload: error.message }); } }; const getMostPopular = () => async (dispatch) => { try { dispatch({ type: MOVIES_SORT_REQUEST }); const data = await sanityAPI.fetch( ` *[_type == 'movie']{ _id, "overview": { "text": overview[0].children[0].text }, "poster" : poster.asset->url, title }| order(popularity desc) [0..2]` ); dispatch({ type: MOVIES_MOST_POPULAR_SUCCESS, payload: data }); } catch (error) { dispatch({ type: MOVIES_MOST_POPULAR_FAIL, payload: error.message }); } }; export { fetchAllMovies, fetchMovieById, sortMoviesBy, getMostPopular, fetchMoviesByRef };

Configuration des réducteurs

Les réducteurs sont l'un des concepts les plus importants de Redux. Ils prennent l'état précédent et déterminent les changements d'état.

En règle générale, nous utiliserons l'instruction switch pour exécuter une condition pour chaque type d'action. Par exemple, nous pouvons renvoyer le loading lorsque le type d'action indique le chargement, puis la charge utile lorsqu'il indique le succès ou l'erreur. On s'attend à ce qu'il prenne l' initial state et l' action comme arguments.

Notre fichier movieReducers.js contient divers réducteurs pour correspondre aux actions définies dans le fichier movieActions.js . Cependant, chacun des réducteurs a une syntaxe et une structure similaires. Les seules différences sont les constants qu'ils appellent et les valeurs qu'ils renvoient.

Commençons par jeter un œil au fetchAllMoviesReducer dans le fichier movieReducers.js .

 import { MOVIES_FETCH_FAIL, MOVIES_FETCH_REQUEST, MOVIES_FETCH_SUCCESS, } from "../constants/movieConstants"; const fetchAllMoviesReducer = (state = {}, action) => { switch (action.type) { case MOVIES_FETCH_REQUEST: return { loading: true }; case MOVIES_FETCH_SUCCESS: return { loading: false, movies: action.payload }; case MOVIES_FETCH_FAIL: return { loading: false, error: action.payload }; case MOVIES_FETCH_RESET: return {}; default: return state; } };

Comme tous les réducteurs, le fetchAllMoviesReducer prend l'objet d'état initial ( state ) et l'objet d' action comme arguments. Nous avons utilisé l'instruction switch pour vérifier les types d'action à chaque instant. S'il correspond à MOVIES_FETCH_REQUEST , nous renvoyons loading comme vrai pour nous permettre de montrer un indicateur de chargement à l'utilisateur.

S'il correspond à MOVIES_FETCH_SUCCESS , nous désactivons l'indicateur de chargement puis renvoyons la charge utile de l'action dans une variable movies . Mais s'il s'agit MOVIES_FETCH_FAIL , nous désactivons également le chargement, puis renvoyons l'erreur. Nous voulons également la possibilité de réinitialiser nos films. Cela nous permettra d'effacer les états lorsque nous en aurons besoin.

Nous avons la même structure pour les autres réducteurs. Le movieReducers.js complet est présenté ci-dessous.

 import { MOVIE_FETCH_FAIL, MOVIE_FETCH_REQUEST, MOVIE_FETCH_SUCCESS, MOVIES_FETCH_FAIL, MOVIES_FETCH_REQUEST, MOVIES_FETCH_SUCCESS, MOVIES_SORT_REQUEST, MOVIES_SORT_SUCCESS, MOVIES_SORT_FAIL, MOVIES_MOST_POPULAR_REQUEST, MOVIES_MOST_POPULAR_SUCCESS, MOVIES_MOST_POPULAR_FAIL, MOVIES_FETCH_RESET, MOVIES_REF_FETCH_REQUEST, MOVIES_REF_FETCH_SUCCESS, MOVIES_REF_FETCH_FAIL } from "../constants/movieConstants"; const fetchAllMoviesReducer = (state = {}, action) => { switch (action.type) { case MOVIES_FETCH_REQUEST: return { loading: true }; case MOVIES_FETCH_SUCCESS: return { loading: false, movies: action.payload }; case MOVIES_FETCH_FAIL: return { loading: false, error: action.payload }; case MOVIES_FETCH_RESET: return {}; default: return state; } }; const fetchMoviesByRefReducer = (state = {}, action) => { switch (action.type) { case MOVIES_REF_FETCH_REQUEST: return { loading: true }; case MOVIES_REF_FETCH_SUCCESS: return { loading: false, movies: action.payload }; case MOVIES_REF_FETCH_FAIL: return { loading: false, error: action.payload }; default: return state; } }; const fetchMovieByIdReducer = (state = {}, action) => { switch (action.type) { case MOVIE_FETCH_REQUEST: return { loading: true }; case MOVIE_FETCH_SUCCESS: return { loading: false, movie: action.payload }; case MOVIE_FETCH_FAIL: return { loading: false, error: action.payload }; default: return state; } }; const sortMoviesByReducer = (state = {}, action) => { switch (action.type) { case MOVIES_SORT_REQUEST: return { loading: true }; case MOVIES_SORT_SUCCESS: return { loading: false, movies: action.payload }; case MOVIES_SORT_FAIL: return { loading: false, error: action.payload }; default: return state; } }; const getMostPopularReducer = (state = {}, action) => { switch (action.type) { case MOVIES_MOST_POPULAR_REQUEST: return { loading: true }; case MOVIES_MOST_POPULAR_SUCCESS: return { loading: false, movies: action.payload }; case MOVIES_MOST_POPULAR_FAIL: return { loading: false, error: action.payload }; default: return state; } }; export { fetchAllMoviesReducer, fetchMovieByIdReducer, sortMoviesByReducer, getMostPopularReducer, fetchMoviesByRefReducer };

Nous avons également suivi exactement la même structure pour personReducers.js . Par exemple, la fonction fetchAllPersonsReducer définit les états pour récupérer toutes les personnes dans la base de données.

Ceci est donné dans le code ci-dessous.

 import { PERSONS_FETCH_FAIL, PERSONS_FETCH_REQUEST, PERSONS_FETCH_SUCCESS, } from "../constants/personConstants"; const fetchAllPersonsReducer = (state = {}, action) => { switch (action.type) { case PERSONS_FETCH_REQUEST: return { loading: true }; case PERSONS_FETCH_SUCCESS: return { loading: false, persons: action.payload }; case PERSONS_FETCH_FAIL: return { loading: false, error: action.payload }; default: return state; } };

Tout comme fetchAllMoviesReducer , nous avons défini fetchAllPersonsReducer avec state et action comme arguments. Ce sont des configurations standard pour les réducteurs Redux. Nous avons ensuite utilisé l'instruction switch pour vérifier les types d'action et si elle est de type PERSONS_FETCH_REQUEST , nous renvoyons le chargement comme vrai. Si c'est PERSONS_FETCH_SUCCESS , nous désactivons le chargement et renvoyons la charge utile, et si c'est PERSONS_FETCH_FAIL , nous renvoyons l'erreur.

Combinaison de réducteurs

La fonction combineReducers de Redux nous permet de combiner plusieurs réducteurs et de les transmettre au magasin. Nous combinerons nos réducteurs de films et de personnes dans un fichier index.js dans le dossier des reducers .

Jetons un coup d'œil.

 import { combineReducers } from "redux"; import { fetchAllMoviesReducer, fetchMovieByIdReducer, sortMoviesByReducer, getMostPopularReducer, fetchMoviesByRefReducer } from "./movieReducers"; import { fetchAllPersonsReducer, fetchPersonByIdReducer, countPersonsReducer } from "./personReducers"; import { toggleTheme } from "./globalReducers"; export default combineReducers({ fetchAllMoviesReducer, fetchMovieByIdReducer, fetchAllPersonsReducer, fetchPersonByIdReducer, sortMoviesByReducer, getMostPopularReducer, countPersonsReducer, fetchMoviesByRefReducer, toggleTheme });

Ici, nous avons importé tous les réducteurs du fichier films, personnes et réducteurs globaux et les avons transmis à la fonction combineReducers . La fonction combineReducers prend un objet qui nous permet de passer tous nos réducteurs. Nous pouvons même ajouter un alias aux arguments du processus.

Nous travaillerons sur les globalReducers plus tard.

Nous pouvons maintenant passer les réducteurs dans le fichier Redux store.js . Ceci est illustré ci-dessous.

 import { createStore, applyMiddleware } from "redux"; import thunk from "redux-thunk"; import reducers from "./reducers/index"; export default createStore(reducers, initialState, applyMiddleware(thunk));

Après avoir configuré notre workflow Redux, configurons notre application React.

Configuration de notre application React

Notre application de réaction répertoriera les films et leurs acteurs et membres d'équipage correspondants. Nous utiliserons react-router-dom pour le routage et styled-components stylisés pour styliser l'application. Nous utiliserons également Material UI pour les icônes et certains composants de l'interface utilisateur.

Entrez la commande bash suivante pour installer les dépendances.

 npm install react-router-dom @material-ui/core @material-ui/icons query-string

Voici ce que nous allons construire :

Connecter Redux à notre application React

React-redux livré avec une fonction de fournisseur qui nous permet de connecter notre application au magasin Redux. Pour ce faire, nous devons transmettre une instance du magasin au fournisseur. Nous pouvons le faire dans notre fichier index.js ou App.js

Voici notre fichier index.js.

 import React from "react"; import ReactDOM from "react-dom"; import "./index.css"; import App from "./App"; import { Provider } from "react-redux"; import store from "./redux/store"; ReactDOM.render( <Provider store={store}> <App /> </Provider>, document.getElementById("root") );

Ici, nous avons importé le Provider de react-redux et store de notre magasin Redux. Ensuite, nous avons enveloppé toute notre arborescence de composants avec le fournisseur, en lui transmettant le magasin.

Ensuite, nous avons besoin react-router-dom pour le routage dans notre application React. react react-router-dom est livré avec BrowserRouter , Switch et Route qui peuvent être utilisés pour définir notre chemin et nos routes.

Nous le faisons dans notre fichier App.js Ceci est illustré ci-dessous.

 import React from "react"; import Header from "./components/Header"; import Footer from "./components/Footer"; import { BrowserRouter as Router, Switch, Route } from "react-router-dom"; import MoviesList from "./pages/MoviesListPage"; import PersonsList from "./pages/PersonsListPage"; function App() { return ( <Router> <main className="contentwrap"> <Header /> <Switch> <Route path="/persons/"> <PersonsList /> </Route> <Route path="/" exact> <MoviesList /> </Route> </Switch> </main> <Footer /> </Router> ); } export default App;

Il s'agit d'une configuration standard pour le routage avec react-router-dom. Vous pouvez le vérifier dans leur documentation. Nous avons importé nos composants Header , Footer , PersonsList et MovieList . Nous avons ensuite configuré le react-router-dom en enveloppant tout dans Router et Switch .

Comme nous voulons que nos pages partagent le même en-tête et le même pied de page, nous avons dû passer les composants <Header /> et <Footer /> avant d'envelopper la structure avec Switch . Nous avons également fait la même chose avec l'élément main puisque nous voulons qu'il enveloppe toute l'application.

Nous avons transmis chaque composant à la route en utilisant Route from react-router-dom .

Définir nos pages et composants

Notre application est organisée de manière structurée. Les composants réutilisables sont stockés dans le dossier des components tandis que les pages sont stockées dans le dossier des pages .

Nos pages comprennent movieListPage.js , moviePage.js , PersonListPage.js et PersonPage.js . Le MovieListPage.js répertorie tous les films de notre backend Sanity.io ainsi que les films les plus populaires.

Pour lister tous les films, nous fetchAllMovies dispatch dans notre fichier movieAction.js . Comme nous devons récupérer la liste dès que la page se charge, nous devons la définir dans useEffect . Ceci est illustré ci-dessous.

 import React, { useEffect } from "react"; import { fetchAllMovies } from "../redux/actions/movieActions"; import { useDispatch, useSelector } from "react-redux"; const MoviesListPage = () => { const dispatch = useDispatch(); useEffect(() => { dispatch(fetchAllMovies()); }, [dispatch]); const { loading, error, movies } = useSelector( (state) => state.fetchAllMoviesReducer ); return ( ... ) }; export default MoviesListPage;

Grâce aux useDispatch et useSelector , nous pouvons envoyer des actions Redux et sélectionner les états appropriés dans le magasin Redux. Notez que les états loading , error et movies ont été définis dans nos fonctions Reducer et les ont sélectionnés ici à l'aide du crochet useSelector de React Redux. Ces états, à savoir loading , error et movies deviennent disponibles dès que nous avons envoyé les actions fetchAllMovies() .

Une fois que nous obtenons la liste des films, nous pouvons l'afficher dans notre application en utilisant la fonction map ou comme nous le souhaitons.

Voici le code complet du fichier moviesListPage.js .

import React, {useState, useEffect} from 'react' import {fetchAllMovies, getMostPopular, sortMoviesBy} from "../redux/actions/movieActions" import {useDispatch, useSelector} from "react-redux" import Loader from "../components/BackdropLoader" import {MovieListContainer} from "../styles/MovieStyles.js" import SortIcon from '@material-ui/icons/Sort'; import SortModal from "../components/Modal" import {useLocation, Link} from "react-router-dom" import queryString from "query-string" import {MOVIES_FETCH_RESET} from "../redux/constants/movieConstants" const MoviesListPage = () => { const location = useLocation() const dispatch = useDispatch() const [openSort, setOpenSort] = useState(false) useEffect(()=>{ dispatch(getMostPopular()) const {order, type} = queryString.parse(location.search) if(order && type){ dispatch({ type: MOVIES_FETCH_RESET }) dispatch(sortMoviesBy(order, type)) }else{ dispatch(fetchAllMovies()) } }, [dispatch, location.search]) const {loading: popularLoading, error: popularError, movies: popularMovies } = useSelector(state => state.getMostPopularReducer) const { loading: moviesLoading, error: moviesError, movies } = useSelector(state => state.fetchAllMoviesReducer) const { loading: sortLoading, error: sortError, movies: sortMovies } = useSelector(state => state.sortMoviesByReducer) return ( <MovieListContainer> <div className="mostpopular"> { popularLoading ? <Loader /> : popularError ? popularError : popularMovies && popularMovies.map(movie => ( <Link to={`/movie?id=${movie._id}`} className="popular" key={movie._id} style={{backgroundImage: `url(${movie.poster})`}}> <div className="content"> <h2>{movie.title}</h2> <p>{movie.overview.text.substring(0, 50)}…</p> </div> </Link> )) } </div> <div className="moviespanel"> <div className="top"> <h2>All Movies</h2> <SortIcon onClick={()=> setOpenSort(true)} /> </div> <div className="movieslist"> { moviesLoading ? <Loader /> : moviesError ? moviesError : movies && movies.map(movie =>( <Link to={`/movie?id=${movie._id}`} key={movie._id}> <img className="movie" src={movie.poster} alt={movie.title} /> </Link> )) } { ( sortLoading ? !movies && <Loader /> : sortError ? sortError : sortMovies && sortMovies.map(movie =>( <Link to={`/movie?id=${movie._id}`} key={movie._id}> <img className="movie" src={movie.poster} alt={movie.title} /> </Link> )) ) } </div> </div> <SortModal open={openSort} setOpen={setOpenSort} /> </MovieListContainer> ) } export default MoviesListPage

Nous avons commencé par envoyer l'action getMostPopular movies (cette action sélectionne les films les plus populaires) dans le crochet useEffect . Cela nous permet de récupérer les films les plus populaires dès le chargement de la page. De plus, nous avons permis aux utilisateurs de trier les films en fonction de leur date de releaseDate et de leur popularity . Ceci est géré par l'action sortMoviesBy distribuée dans le code ci-dessus. De plus, nous avons envoyé les fetchAllMovies en fonction des paramètres de la requête.

De plus, nous avons utilisé le crochet useSelector pour sélectionner les réducteurs correspondants pour chacune de ces actions. Nous avons sélectionné les états de loading , error et de movies pour chacun des réducteurs.

Après avoir obtenu les movies des réducteurs, nous pouvons maintenant les afficher à l'utilisateur. Ici, nous avons utilisé la fonction de map ES6 pour ce faire. Nous avons d'abord affiché un chargeur chaque fois que chacun des états du film est en cours de chargement et s'il y a une erreur, nous affichons le message d'erreur. Enfin, si nous obtenons un film, nous affichons l'image du film à l'utilisateur à l'aide de la fonction map . Nous avons enveloppé le composant entier dans un composant MovieListContainer .

La <MovieListContainer> … </MovieListContainer> est une div définie à l'aide de composants stylés. Nous y reviendrons brièvement.

Styliser notre application avec des composants stylisés

Les composants stylisés nous permettent de styliser nos pages et composants sur une base individuelle. Il offre également quelques fonctionnalités intéressantes telles que l' inheritance , Theming , le passing of props , etc.

Bien que nous souhaitions toujours donner un style à nos pages de manière individuelle, un style global peut parfois être souhaitable. Fait intéressant, les composants stylés fournissent un moyen de le faire, grâce à la fonction createGlobalStyle .

Pour utiliser des composants de style dans notre application, nous devons l'installer. Ouvrez votre terminal dans votre projet React et entrez la commande bash suivante.

 npm install styled-components

Après avoir installé les composants de style, commençons avec nos styles globaux.

Créons un dossier séparé dans notre répertoire src nommé styles . Cela stockera tous nos styles. Créons également un fichier globalStyles.js dans le dossier styles. Pour créer un style global dans les composants stylés, nous devons importer createGlobalStyle .

 import { createGlobalStyle } from "styled-components";

Nous pouvons alors définir nos styles comme suit :

 export const GlobalStyle = createGlobalStyle` ... `

Les composants stylisés utilisent le littéral de modèle pour définir les accessoires. Dans ce littéral, nous pouvons écrire nos codes CSS traditionnels.

Nous avons également importé deviceWidth défini dans un fichier nommé definition.js . Le deviceWidth contient la définition des points d'arrêt pour définir nos requêtes multimédias.

 import { deviceWidth } from "./definition";

Nous définissons le débordement sur masqué pour contrôler le flux de notre application.

 html, body{ overflow-x: hidden; }

Nous avons également défini le style d'en-tête à l'aide du sélecteur de style .header .

 .header{ z-index: 5; background-color: ${(props)=>props.theme.midDarkBlue}; display:flex; align-items:center; padding: 0 20px; height:50px; justify-content:space-between; position:fixed; top:0; width:100%; @media ${deviceWidth.laptop_lg} { width:97%; } ... }

Ici, divers styles tels que la couleur d'arrière-plan, le z-index, le rembourrage et de nombreuses autres propriétés CSS traditionnelles sont définis.

Nous avons utilisé les props styled-components pour définir la couleur d'arrière-plan. Cela nous permet de définir des variables dynamiques qui peuvent être transmises depuis notre composant. De plus, nous avons également passé la variable du thème pour nous permettre de tirer le meilleur parti de notre basculement de thème.

La thématisation est possible ici car nous avons enveloppé toute notre application avec le ThemeProvider des composants stylés. Nous en reparlerons dans un instant. De plus, nous avons utilisé la CSS flexbox correctement notre en-tête et définir la position sur fixed pour nous assurer qu'il reste fixe par rapport au navigateur. Nous avons également défini les points d'arrêt pour rendre les en-têtes compatibles avec les mobiles.

Voici le code complet de notre fichier globalStyles.js .

 import { createGlobalStyle } from "styled-components"; import { deviceWidth } from "./definition"; export const GlobalStyle = createGlobalStyle` html{ overflow-x: hidden; } body{ background-color: ${(props) => props.theme.lighter}; overflow-x: hidden; min-height: 100vh; display: grid; grid-template-rows: auto 1fr auto; } #root{ display: grid; flex-direction: column; } h1,h2,h3, label{ font-family: 'Aclonica', sans-serif; } h1, h2, h3, p, span:not(.MuiIconButton-label), div:not(.PrivateRadioButtonIcon-root-8), div:not(.tryingthis){ color: ${(props) => props.theme.bodyText} } p, span, div, input{ font-family: 'Jost', sans-serif; } .paginate button{ color: ${(props) => props.theme.bodyText} } .header{ z-index: 5; background-color: ${(props) => props.theme.midDarkBlue}; display: flex; align-items: center; padding: 0 20px; height: 50px; justify-content: space-between; position: fixed; top: 0; width: 100%; @media ${deviceWidth.laptop_lg}{ width: 97%; } @media ${deviceWidth.tablet}{ width: 100%; justify-content: space-around; } a{ text-decoration: none; } label{ cursor: pointer; color: ${(props) => props.theme.goldish}; font-size: 1.5rem; } .hamburger{ cursor: pointer; color: ${(props) => props.theme.white}; @media ${deviceWidth.desktop}{ display: none; } @media ${deviceWidth.tablet}{ display: block; } } } .mobileHeader{ z-index: 5; background-color: ${(props) => props.theme.darkBlue}; color: ${(props) => props.theme.white}; display: grid; place-items: center; width: 100%; @media ${deviceWidth.tablet}{ width: 100%; } height: calc(100% - 50px); transition: all 0.5s ease-in-out; position: fixed; right: 0; top: 50px; .menuitems{ display: flex; box-shadow: 0 0 5px ${(props) => props.theme.lightshadowtheme}; flex-direction: column; align-items: center; justify-content: space-around; height: 60%; width: 40%; a{ display: flex; flex-direction: column; align-items:center; cursor: pointer; color: ${(props) => props.theme.white}; text-decoration: none; &:hover{ border-bottom: 2px solid ${(props) => props.theme.goldish}; .MuiSvgIcon-root{ color: ${(props) => props.theme.lightred} } } } } } footer{ min-height: 30px; margin-top: auto; display: flex; flex-direction: column; align-items: center; justify-content: center; font-size: 0.875rem; background-color: ${(props) => props.theme.midDarkBlue}; color: ${(props) => props.theme.white}; } `;

Notez que nous avons écrit du code CSS pur dans le littéral, mais il y a quelques exceptions. Les composants de style nous permettent de passer des accessoires. Vous pouvez en savoir plus à ce sujet dans la documentation.

En plus de définir des styles globaux, nous pouvons définir des styles pour des pages individuelles.

Par exemple, voici le style de PersonListPage.js défini dans PersonStyle.js dans le dossier styles .

 import styled from "styled-components"; import { deviceWidth, colors } from "./definition"; export const PersonsListContainer = styled.div` margin: 50px 80px; @media ${deviceWidth.tablet} { margin: 50px 10px; } a { text-decoration: none; } .top { display: flex; justify-content: flex-end; padding: 5px; .MuiSvgIcon-root { cursor: pointer; &:hover { color: ${colors.darkred}; } } } .personslist { margin-top: 20px; display: grid; place-items: center; grid-template-columns: repeat(5, 1fr); @media ${deviceWidth.laptop} { grid-template-columns: repeat(4, 1fr); } @media ${deviceWidth.tablet} { grid-template-columns: repeat(3, 1fr); } @media ${deviceWidth.tablet_md} { grid-template-columns: repeat(2, 1fr); } @media ${deviceWidth.mobile_lg} { grid-template-columns: repeat(1, 1fr); } grid-gap: 30px; .person { width: 200px; position: relative; img { width: 100%; } .content { position: absolute; bottom: 0; left: 8px; border-right: 2px solid ${colors.goldish}; border-left: 2px solid ${colors.goldish}; border-radius: 10px; width: 80%; margin: 20px auto; padding: 8px 10px; background-color: ${colors.transparentWhite}; color: ${colors.darkBlue}; h2 { font-size: 1.2rem; } } } } `;

Nous avons d'abord importé styled de styled styled-components et deviceWidth du fichier de definition . Nous avons ensuite défini PersonsListContainer comme une div pour contenir nos styles. À l'aide des requêtes multimédias et des points d'arrêt établis, nous avons rendu la page adaptée aux mobiles en définissant divers points d'arrêt.

Ici, nous n'avons utilisé que les points d'arrêt standard du navigateur pour les petits, grands et très grands écrans. Nous avons également tiré le meilleur parti de la boîte flexible et de la grille CSS pour styliser et afficher correctement notre contenu sur la page.

Pour utiliser ce style dans notre fichier PersonListPage.js , nous l'avons simplement importé et ajouté à notre page comme suit.

 import React from "react"; const PersonsListPage = () => { return ( <PersonsListContainer> ... </PersonsListContainer> ); }; export default PersonsListPage;

Le wrapper affichera un div car nous l'avons défini comme un div dans nos styles.

Ajouter des thèmes et conclure

C'est toujours une fonctionnalité intéressante d'ajouter des thèmes à notre application. Pour cela, nous avons besoin des éléments suivants :

  • Nos thèmes personnalisés définis dans un fichier séparé (dans notre cas, le fichier definition.js ).
  • La logique définie dans nos actions et réducteurs Redux.
  • Appeler notre thème dans notre application et le passer à travers l'arborescence des composants.

Vérifions ça.

Voici notre objet theme dans le fichier definition.js .

 export const theme = { light: { dark: "#0B0C10", darkBlue: "#253858", midDarkBlue: "#42526e", lightBlue: "#0065ff", normal: "#dcdcdd", lighter: "#F4F5F7", white: "#FFFFFF", darkred: "#E85A4F", lightred: "#E98074", goldish: "#FFC400", bodyText: "#0B0C10", lightshadowtheme: "rgba(0, 0, 0, 0.1)" }, dark: { dark: "white", darkBlue: "#06090F", midDarkBlue: "#161B22", normal: "#dcdcdd", lighter: "#06090F", white: "white", darkred: "#E85A4F", lightred: "#E98074", goldish: "#FFC400", bodyText: "white", lightshadowtheme: "rgba(255, 255, 255, 0.9)" } };

Nous avons ajouté diverses propriétés de couleur pour les thèmes clairs et sombres. Les couleurs sont soigneusement choisies pour permettre une visibilité aussi bien en mode clair qu'en mode sombre. Vous pouvez définir vos thèmes comme vous le souhaitez. Ce n'est pas une règle absolue.

Ensuite, ajoutons la fonctionnalité à Redux.

Nous avons créé globalActions.js dans notre dossier d'actions Redux et ajouté les codes suivants.

 import { SET_DARK_THEME, SET_LIGHT_THEME } from "../constants/globalConstants"; import { theme } from "../../styles/definition"; export const switchToLightTheme = () => (dispatch) => { dispatch({ type: SET_LIGHT_THEME, payload: theme.light }); localStorage.setItem("theme", JSON.stringify(theme.light)); localStorage.setItem("light", JSON.stringify(true)); }; export const switchToDarkTheme = () => (dispatch) => { dispatch({ type: SET_DARK_THEME, payload: theme.dark }); localStorage.setItem("theme", JSON.stringify(theme.dark)); localStorage.setItem("light", JSON.stringify(false)); };

Ici, nous avons simplement importé nos thèmes définis. Distribué les actions correspondantes, en transmettant la charge utile des thèmes dont nous avions besoin. Les résultats de la charge utile sont stockés dans le stockage local en utilisant les mêmes clés pour les thèmes clairs et sombres. Cela nous permet de conserver les états dans le navigateur.

Nous devons également définir notre réducteur pour les thèmes.

 import { SET_DARK_THEME, SET_LIGHT_THEME } from "../constants/globalConstants"; export const toggleTheme = (state = {}, action) => { switch (action.type) { case SET_LIGHT_THEME: return { theme: action.payload, light: true }; case SET_DARK_THEME: return { theme: action.payload, light: false }; default: return state; } };

C'est très similaire à ce que nous avons fait. Nous avons utilisé l'instruction switch pour vérifier le type d'action, puis nous avons renvoyé la payload appropriée. Nous avons également renvoyé un état light qui détermine si le thème clair ou sombre est sélectionné par l'utilisateur. Nous allons l'utiliser dans nos composants.

Nous devons également l'ajouter à notre réducteur de racines et à notre magasin. Voici le code complet de notre store.js .

 import { createStore, applyMiddleware } from "redux"; import thunk from "redux-thunk"; import { theme as initialTheme } from "../styles/definition"; import reducers from "./reducers/index"; const theme = localStorage.getItem("theme") ? JSON.parse(localStorage.getItem("theme")) : initialTheme.light; const light = localStorage.getItem("light") ? JSON.parse(localStorage.getItem("light")) : true; const initialState = { toggleTheme: { light, theme } }; export default createStore(reducers, initialState, applyMiddleware(thunk));

Étant donné que nous devions conserver le thème lorsque l'utilisateur actualisait, nous devions l'obtenir à partir du stockage local à l'aide de localStorage.getItem() et le transmettre à notre état initial.

Ajout de la fonctionnalité à notre application React

Les composants stylés nous fournissent ThemeProvider qui nous permet de transmettre des thèmes via notre application. Nous pouvons modifier notre fichier App.js pour ajouter cette fonctionnalité.

Jetons un coup d'œil.

 import React from "react"; import { BrowserRouter as Router, Switch, Route } from "react-router-dom"; import { useSelector } from "react-redux"; import { ThemeProvider } from "styled-components"; function App() { const { theme } = useSelector((state) => state.toggleTheme); let Theme = theme ? theme : {}; return ( <ThemeProvider theme={Theme}> <Router> ... </Router> </ThemeProvider> ); } export default App;

En passant des thèmes via le ThemeProvider , nous pouvons facilement utiliser les accessoires de thème dans nos styles.

Par exemple, nous pouvons définir la couleur sur notre couleur personnalisée bodyText comme suit.

 color: ${(props) => props.theme.bodyText};

Nous pouvons utiliser les thèmes personnalisés partout où nous avons besoin de couleur dans notre application.

Par exemple, pour définir border-bottom , nous procédons comme suit.

 border-bottom: 2px solid ${(props) => props.theme.goldish};

Conclusion

Nous avons commencé par plonger dans Sanity.io, en le configurant et en le connectant à notre application React. Ensuite, nous avons configuré Redux et utilisé le langage GROQ pour interroger notre API. Nous avons vu comment connecter et utiliser Redux à notre application React en utilisant react-redux , utiliser des composants stylés et des thèmes.

Cependant, nous n'avons fait qu'effleurer ce qui est possible avec ces technologies. Je vous encourage à parcourir les exemples de code de mon référentiel GitHub et à vous essayer à un projet complètement différent en utilisant ces technologies pour les apprendre et les maîtriser.

Ressources

  • Documentation sur la santé mentale
  • Comment créer un blog avec Sanity.io par Kapehe
  • Documentation redux
  • Documentation sur les composants stylés
  • Aide-mémoire GROQ
  • Documentation de l'interface utilisateur des matériaux
  • Intergiciel Redux et effets secondaires
  • Documentation sur les thunks Redux