Fermer

février 11, 2021

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


À propos de l'auteur

Ifeanyi Dike est un développeur full-stack à Abuja, au Nigeria. Il est le chef d'équipe de Sterling Digitals Limited, mais il est également ouvert à plus d'opportunités et…
En savoir plus sur
Ifeanyi

Headless CMS est un moyen puissant et simple de gérer le contenu et d'accéder à l'API. Construit 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, nous allons créer une application de liste simple avec Sanity.io et React. Nos états globaux seront gérés avec Redux et l'application sera stylée avec des composants stylisés.

L'évolution rapide des plates-formes numériques a placé de sérieuses limitations sur les CMS traditionnels comme WordPress. Ces plates-formes sont couplées, inflexibles et se concentrent 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 applications numériques écrans, 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, destiné aux développeurs et offrant une prise en charge multiplateforme, vous n’avez pas besoin de chercher plus loin que le CMS sans tête. La tête fait ici référence au frontend ou à la couche de présentation tandis que le corps fait référence au backend ou au référentiel de contenu. Cela offre de nombreux avantages intéressants. Par exemple, il permet au développeur de choisir n'importe quelle interface de son choix et vous pouvez également concevoir le calque de présentation comme vous le souhaitez.

Il existe de nombreux CMS sans tête, parmi les plus populaires, Strapi, Contentful, Contentstack , Sanity, Butter CMS, Prismic, Storyblok, Directus, etc. Ces CMS headless 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 headless, chacune de ces plates-formes peut être branchée sur n'importe quel frontend, qu'il soit Angular, Vue ou React.

Chacun de ces CMS headless a à la fois des plans gratuits et payants qui représentent un saut de prix significatif. Bien que ces plans payants offrent plus de fonctionnalités, vous ne voudriez pas payer autant pour un projet de petite à moyenne taille. Sanity tente de résoudre ce problème en introduisant des options de paiement à l'utilisation. Avec ces options, vous serez en mesure de payer pour ce que vous utilisez et d'éviter le saut de prix.

Une autre raison pour laquelle j'ai choisi Sanity.io est leur langage GROQ. Pour moi, Sanity se démarque de la foule 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 changement de code.

De plus, les développeurs ne sont pas limités au langage GROQ. Vous pouvez également utiliser GraphQL ou même les traditionnels axios 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 qui contient des conseils utiles pour construire sur la plate-forme.

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

Premiers pas avec Sanity.io

Pour utiliser Sanity sur votre machine, vous devrez installer l'outil CLI Sanity. 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 de la commande ci-dessus permet une 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 frontend (dans ce cas React).

Dans son ​​blog Kapehe a expliqué en détail comment intégrer Sanity avec Réagir. 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 pour nous 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 le invite à 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é de Sanity. Cela nous évitera de saisir les données nous-mêmes.

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

 Vue d'ensemble du serveur Sanity
Vue d'ensemble du serveur Sanity pour l'ensemble de données du film de science-fiction. ( Grand aperçu )

Communication bidirectionnelle Sanity-React

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

CORS Origins Setting in 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 les Settings languette. Ici, vous devrez relier l'origine de votre frontend au backend Sanity. Notre application React fonctionne sur http: // 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 [19659029] Définition de l'origine CORS dans Sanity.io Manager. (<a href= Grand aperçu )

Connecter Sanity To React

Sanity associe un ID de projet à 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 en utilisant une bibliothèque connue sous le nom de sanity client . Vous devez installer cette bibliothèque dans votre projet Sanity en entrant les commandes suivantes.

 npm install @ sanity / client 

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

 import sanityClient depuis "@ sanity / client"
exporter par défaut sanityClient ({
    projectId: PROJECT_ID,
    ensemble de données: DATASET_NAME,
    useCdn: vrai
}); 

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

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

Configurer et connecter 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 et bibliothèques frontend tels 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. Redux thunk nous aidera à renvoyer une fonction au lieu d'un objet action de Redux.

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

Configurer le magasin

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

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

 import {createStore, applyMiddleware} de "redux";
importer le thunk de "redux-thunk";
importer des réducteurs de "./reducers/";

exporter par défaut createStore (
  réducteurs,
  applyMiddleware (thunk)
);

La fonction createStore de ce fichier prend trois paramètres: le reducer (obligatoire), l'état initial et le enhancer (généralement un middleware, dans ce cas, thunk fourni via applyMiddleware ). Nos réducteurs seront stockés dans un dossier réducteurs 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 fichier plus tard.

Introduction au langage GROQ de Sanity

Sanity va encore plus loin dans l'interrogation des données JSON en présentant GROQ. GROQ est l'acronyme de 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 la langue. Cependant, pour accéder au terrain de jeu, 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 film, nous utiliserons la syntaxe GROQ suivante.

 * [_type == "movie"] 

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

 `* [_type == 'movie'] {
    _id,
    membres d'équipage
}

Ici, nous avons utilisé * pour dire à GROQ que nous voulons chaque document du film _type . _type est un attribut de la collection de films. Nous pouvons également renvoyer le type comme nous l'avons fait pour _id et crewMembers comme suit:

 * [_type == 'movie'] {
    _id,
    _type,
    membres d'équipage
}

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. Le Aide-mémoire 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 distribuée à chaque instant. Par exemple, nous pouvons suivre quand l'API est en cours de chargement, complètement chargée et lorsqu'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 ici les bonnes pratiques pour définir nos constantes. Voici un exemple de constante pour indiquer les demandes de récupération de films en mouvement.

 export const MOVIE_FETCH_REQUEST = "MOVIE_FETCH_REQUEST"; 

Ici, nous avons créé une constante MOVIE_FETCH_REQUEST qui indique un type d'action de ] MOVIE_FETCH_REQUEST . Cela nous aide à appeler facilement ce type d'action sans utiliser les chaînes 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.

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 en cours de chargement réussie et a échoué .

De même, notre personConstants.js le fichier 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 ou plusieurs personnes. Nous définissons également une constante pour compter les personnes. Les constantes suivent la convention décrite pour movieConstants.js et nous les avons également exportées pour être accessibles à d'autres parties de notre application.

Enfin, nous implémenterons le mode clair et sombre dans l'application et nous avoir 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 distribué. 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é.

Configuration des actions

Par convention, nos actions sont stockées dans un dossier séparé. Les actions sont regroupées selon leurs types. 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 prendre soin de faire basculer le thème du mode clair au mode sombre.

Allons chercher tous les films dans moviesActions.js .

 import sanityAPI de "../../sanitySetup";
importer {
  MOVIES_FETCH_FAIL,
  MOVIES_FETCH_REQUEST,
  MOVIES_FETCH_SUCCESS
} de "../constants/movieConstants";

const fetchAllMovies = () => async (répartition) => {
  essayez {
    envoi({
      type: MOVIES_FETCH_REQUEST
    });
    const data = attendre sanityAPI.fetch (
      `* [_type == 'movie'] {
          _id,
          "affiche": poster.asset-> url,
      } `
    );
    envoi({
      tapez: MOVIES_FETCH_SUCCESS,
      charge utile: données
    });
  } catch (erreur) {
    envoi({
      tapez: MOVIES_FETCH_FAIL,
      charge utile: 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 de santé mentale à l'aide de GROQ. Nous avons également importé quelques constantes exportées du fichier movieConstants.js dans le dossier constants .

Ensuite, nous avons créé la fonction d'action fetchAllMovies pour récupérer chaque film dans 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 de ces éléments ici, nous utilisons le GROQ de Sanity. Pour entrer dans le 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 établie précédemment. Cela renvoie une Promise et doit donc être appelée 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 action. Cependant, nous avons choisi de transmettre l'instruction return en 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 distribué un type d'action qui suit le chargement de la requête. 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 les films par leur _id, trier les films et obtenir les films les plus populaires.

Nous pouvons également récupérer les 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) => {
  essayez {
    envoi({
      type: MOVIES_REF_FETCH_REQUEST
    });
    const data = attendre sanityAPI.fetch (
      `* [_type == 'movie' 
            && (castMembers[person._ref match '${ref}'] ||
                membres d'équipage [person._ref match '${ref}'])
            ] {
                _id,
                "poster": poster.asset-> url,
                Titre
            } `
    );
    envoi({
      tapez: MOVIES_REF_FETCH_SUCCESS,
      charge utile: données
    });
  } catch (erreur) {
    envoi({
      tapez: MOVIES_REF_FETCH_FAIL,
      charge utile: error.message
    });
  }
}; 

Cette fonction prend un argument et vérifie si person._ref dans castMembers ou crewMembers correspond à l'argument passé. Nous retournons le film _id l'url de l'affiche et le titre à côté. Nous envoyons également une action de type MOVIES_REF_FETCH_SUCCESS en joignant 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 joignant 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 identifiant particulier passé à la fonction.

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

 const data = await sanityAPI.fetch (
      `* [_type == 'movie' && _id == '${id}'] {
                _id,
                "cast":
                    castMembers [] {
                        "ref": person._ref,
                        le nom du personnage,
                        "nom": personne-> nom,
                        "image": personne-> image.asset-> url
                    }
                ,
                "équipage" :
                    crewMembers [] {
                        "ref": person._ref,
                        département,
                        emploi,
                        "nom": personne-> nom,
                        "image": personne-> image.asset-> url
                    }
                ,
                "Aperçu":   {
                    "text": vue d'ensemble [0] .children [0] .text
                  },
                popularité,
                "affiche": poster.asset-> url,
                date de sortie,
                Titre
            } [0] `
    ); 

Comme l'action fetchAllMovies nous avons commencé par sélectionner tous les documents de type movie mais nous sommes allés plus loin en ne sélectionnant que ceux avec un identifiant 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é le film id et aussi quelques attributs dans le castMembers tableau à savoir ref characterName le nom de la personne et l'image de la personne. Nous avons également changé l'alias de castMembers à cast .

Comme pour castMembers nous avons sélectionné quelques attributs du tableau crewMembers , à savoir réf département job le nom et l'image de la personne. nous avons également changé l'alias de crewMembers à crew .

De la même manière, nous avons sélectionné le texte général, 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 élément, nous passons l'ordre à côté d'un opérateur pipe .

Par exemple, si nous souhaitons trier les films par leur date de sortie dans l'ordre croissant , nous pourrions faire ce qui suit.

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

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

Examinons cette fonction ci-dessous.

 const sortMoviesBy = (item, type) = > async (répartition) => {
  essayez {
    envoi({
      type: MOVIES_SORT_REQUEST
    });
    const data = attendre sanityAPI.fetch (
      `* [_type == 'movie'] {
                _id,
                "affiche": poster.asset-> url,
                Titre
                } | commande ($ {item} $ {type}) `
    );
    envoi({
      tapez: MOVIES_SORT_SUCCESS,
      charge utile: données
    });
  } catch (erreur) {
    envoi({
      tapez: MOVIES_SORT_FAIL,
      charge utile: 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 movie . L'élément à trier est fourni dans la variable item et le mode de tri (croissant 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 renvoyées, nous envoyons une action de type MOVIES_SORT_SUCCESS et en cas d'échec, nous envoyons une action de type MOVIES_SORT_FAIL .

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

 const data = await sanityAPI.fetch (
      »
            * [_type == 'movie'] {
                _id,
                "Aperçu":   {
                    "text": vue d'ensemble [0] .children [0] .text
                },
                "affiche": poster.asset-> url,
                Titre
            } | ordre (popularité 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 la version complète code pour les actions de film dans le fichier movieActions.js .

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 général, nous utiliserons l'instruction switch pour exécuter une condition pour chaque type d'action. Par exemple, nous pouvons renvoyer loading lorsque le type d'action dénote un chargement, puis la charge utile lorsqu'il dénote un succès ou une erreur. On s'attend à ce qu'il prenne comme arguments l'état initial et l'action .

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 constantes qu'elles appellent et les valeurs qu'elles renvoient.

Commençons par examiner le fetchAllMoviesReducer dans le fichier movieReducers.js .

 import {
  MOVIES_FETCH_FAIL,
  MOVIES_FETCH_REQUEST,
  MOVIES_FETCH_SUCCESS,
} de "../constants/movieConstants";

const fetchAllMoviesReducer = (état = {}, action) => {
  commutateur (action.type) {
    case MOVIES_FETCH_REQUEST:
      revenir {
        chargement: vrai
      };
    case MOVIES_FETCH_SUCCESS:
      revenir {
        chargement: faux,
        films: action.payload
      };
    case MOVIES_FETCH_FAIL:
      revenir {
        chargement: faux,
        erreur: action.payload
      };
    case MOVIES_FETCH_RESET:
      revenir {};
    défaut:
      état de retour;
  }
}; 

Comme tous les réducteurs, le fetchAllMoviesReducer prend comme arguments l'objet d'état initial ( état ) et l'objet d'action . Nous avons utilisé l'instruction switch pour vérifier les types d'actions à chaque instant. S'il correspond à MOVIES_FETCH_REQUEST nous renvoyons le chargement 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 renvoyez la charge utile d'action dans une variable movies . Mais s'il s'agit de 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 montré ci-dessous.

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,
} de "../constants/personConstants";

const fetchAllPersonsReducer = (état = {}, action) => {
  commutateur (action.type) {
    case PERSONS_FETCH_REQUEST:
      revenir {
        chargement: vrai
      };
    case PERSONS_FETCH_SUCCESS:
      revenir {
        chargement: faux,
        personnes: action.payload
      };
    case PERSONS_FETCH_FAIL:
      revenir {
        chargement: faux,
        erreur: action.payload
      };
    défaut:
      état de retour;
  }
};

Tout comme le 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. S'il s'agit de PERSONS_FETCH_SUCCESS nous désactivons le chargement et renvoyons la charge utile, et s'il s'agit de PERSONS_FETCH_FAIL nous renvoyons l'erreur.

Combining Reducer

Redux's moissonneuse-réducteurs ] nous permet de combiner plus d'un réducteur et de le transmettre au magasin. Nous allons combiner nos films et nos réducteurs de personnes dans un fichier index.js dans le dossier reducers .

Jetons un œil.

 import {combineReducers} de "redux";
importer {
  fetchAllMoviesReducer,
  fetchMovieByIdReducer,
  sortMoviesByReducer,
  getMostPopularReducer,
  fetchMoviesByRefReducer
} de "./movieReducers";

importer {
  fetchAllPersonsReducer,
  fetchPersonByIdReducer,
  countPersonsReducer
} de "./personReducers";

import {toggleTheme} de "./globalReducers";

exporter par défaut combineReducers ({
  fetchAllMoviesReducer,
  fetchMovieByIdReducer,
  fetchAllPersonsReducer,
  fetchPersonByIdReducer,
  sortMoviesByReducer,
  getMostPopularReducer,
  countPersonsReducer,
  fetchMoviesByRefReducer,
  toggleThème
}); 

Ici, nous avons importé tous les réducteurs du fichier des films, des personnes et des réducteurs globaux et les avons passés à 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 dans le processus.

Nous travaillerons sur les globalReducers plus tard.

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

 import {createStore, applyMiddleware} de "redux";
importer le thunk de "redux-thunk";
importer des réducteurs depuis "./reducers/index";

export par défaut createStore (réducteurs, initialState, applyMiddleware (thunk));

Après avoir configuré notre flux de travail Redux, configurons notre application react.

Configuration de notre application React

Notre application react répertorie les films et les acteurs et membres d'équipage correspondants. Nous utiliserons react-router-dom pour le routage et styled-components pour styliser l'application. Nous utiliserons également l'interface utilisateur matérielle 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.

Connexion de Redux à notre application React

React-redux est livré avec un fournisseur fonction 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 soit dans notre fichier index.js ou App.js .

Voici notre fichier index.js.

 import React de "react";
importer ReactDOM depuis "react-dom";
import "./index.css";
importer l'application depuis "./App";
import {Provider} de "react-redux";
importer le magasin depuis "./redux/store";
ReactDOM.render (
  
    
  ,
  document.getElementById ("racine")
); 

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

Ensuite, nous avons besoin de react-router-dom pour le routage dans notre application 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 faisons cela dans notre fichier App.js . Ceci est montré ci-dessous:

 import React de "react";
importer l'en-tête de "./components/Header";
importer le pied de page de "./components/Footer";
importer {BrowserRouter en tant que routeur, commutateur, route} de "react-router-dom";
importer MoviesList depuis "./pages/MoviesListPage";
importation de PersonsList depuis "./pages/PersonsListPage";

function App () {

  revenir (
      
        
); } 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 le tout dans Router et Switch .

Puisque nous voulons que nos pages partagent le même en-tête et pied de page, nous avons dû passer les composants

et
avant d'envelopper la structure avec Switch . We also did a similar thing with the main element since we want it to wrap the entire application.

We passed each component to the route using Route from react-router-dom.

Defining Our Pages And Components

Our application is organized in a structured way. Reusable components are stored in the components folder while Pages are stored in the pages folder.

Our pages comprise movieListPage.jsmoviePage.jsPersonListPage.js and PersonPage.js. The MovieListPage.js lists all the movies in our Sanity.io backend as well as the most popular movies.

To list all the movies, we simply dispatch the fetchAllMovies action defined in our movieAction.js file. Since we need to fetch the list as soon as the page loads, we have to define it in the useEffect. This is shown below.

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;

Thanks to the useDispatch and useSelector Hooks, we can dispatch Redux actions and select the appropriate states from the Redux store. Notice that the states loadingerror and movies were defined in our Reducer functions and here selected them using the useSelector Hook from React Redux. These states namely loadingerror and movies become available immediately we dispatched the fetchAllMovies() actions.

Once we get the list of movies, we can display it in our application using the map function or however we wish.

Here is the complete code for the moviesListPage.js file.

We started by dispatching the getMostPopular movies action (this action selects the movies with the highest popularity) in the useEffect Hook. This allows us to retrieve the most popular movies as soon as the page loads. Additionally, we allowed users to sort movies by their releaseDate and popularity. This is handled by the sortMoviesBy action dispatched in the code above. Furthermore, we dispatched the fetchAllMovies depending on the query parameters.

Also, we used the useSelector Hook to select the corresponding reducers for each of these actions. We selected the states for loadingerror and movies for each of the reducers.

After getting the movies from the reducers, we can now display them to the user. Here, we have used the ES6 map function to do this. We first displayed a loader whenever each of the movie states is loading and if there’s an error, we display the error message. Finally, if we get a movie, we display the movie image to the user using the map function. We wrapped the entire component in a MovieListContainer component.

The tag is a div defined using styled components. We’ll take a brief look at that soon.

Styling Our App With Styled Components

Styled components allow us to style our pages and components on an individual basis. It also offers some interesting features such as inheritanceThemingpassing of propsetc.

Although we always want to style our pages on an individual basis, sometimes global styling may be desirable. Interestingly, styled-components provide a way to do that, thanks to the createGlobalStyle function.

To use styled-components in our application, we need to install it. Open your terminal in your react project and enter the following bash command.

npm install styled-components

Having installed styled-components, Let’s get started with our global styles.

Let’s create a separate folder in our src directory named styles. This will store all our styles. Let’s also create a globalStyles.js file within the styles folder. To create global style in styled-components, we need to import createGlobalStyle.

import { createGlobalStyle } from "styled-components";

We can then define our styles as follows:

export const GlobalStyle = createGlobalStyle`
  ...
`

Styled components make use of the template literal to define props. Within this literal, we can write our traditional CSS codes.

We also imported deviceWidth defined in a file named definition.js. The deviceWidth holds the definition of breakpoints for setting our media queries.

import { deviceWidth } from "./definition";

We set overflow to hidden to control the flow of our application.

html, body{
        overflow-x: hidden;
}

We also defined the header style using the .header style selector.

.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%;
  }
  ...
}

Here, various styles such as the background color, z-index, padding, and lots of other traditional CSS properties are defined.

We’ve used the styled-components props to set the background color. This allows us to set dynamic variables that can be passed from our component. Moreover, we also passed the theme’s variable to enable us to make the most of our theme toggling.

Theming is possible here because we have wrapped our entire application with the ThemeProvider from styled-components. We’ll talk about this in a moment. Furthermore, we used the CSS flexbox to properly style our header and set the position to fixed to make sure it remains fixed with respect to the browser. We also defined the breakpoints to make the headers mobile friendly.

Here is the complete code for our globalStyles.js file.

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{
            texte-décoration: aucun;
        }
        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};
                texte-décoration: aucun;
                &: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};        
    }
`;

Notice that we wrote pure CSS code within the literal but there are a few exceptions. Styled-components allows us to pass props. You can learn more about this in the documentation.

Apart from defining global styles, we can define styles for individual pages.

For instance, here is the style for the PersonListPage.js defined in PersonStyle.js in the styles folder.

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 {
    texte-décoration: aucun;
  }
  .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;
        }
      }
    }
  }
`;

We first imported styled from styled-components and deviceWidth from the definition file. We then defined PersonsListContainer as a div to hold our styles. Using media queries and the established breakpoints, we made the page mobile-friendly by setting various breakpoints.

Here, we have used only the standard browser breakpoints for small, large and very large screens. We also made the most of the CSS flexbox and grid to properly style and display our content on the page.

To use this style in our PersonListPage.js file, we simply imported it and added it to our page as follows.

import React from "react";

const PersonsListPage = () => {
  return (
    
      ...
    
  );
};
export default PersonsListPage;

The wrapper will output a div because we defined it as a div in our styles.

Adding Themes And Wrapping It Up

It’s always a cool feature to add themes to our application. For this, we need the following:

  • Our custom themes defined in a separate file (in our case definition.js file).
  • The logic defined in our Redux actions and reducers.
  • Calling our theme in our application and passing it through the component tree.

Let’s check this out.

Here is our theme object in the definition.js file.

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)"
  }
};

We have added various color properties for the light and dark themes. The colors are carefully chosen to enable visibility both in light and dark mode. You can define your themes as you want. This is not a hard and fast rule.

Next, let’s add the functionality to Redux.

We have created globalActions.js in our Redux actions folder and added the following codes.

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));
};

Here, we simply imported our defined themes. Dispatched the corresponding actions, passing the payload of the themes we needed. The payload results are stored in the local storage using the same keys for both light and dark themes. This enables us to persist the states in the browser.

We also need to define our reducer for the themes.

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;
  }
};

This is very similar to what we’ve been doing. We used the switch statement to check the type of action and then returned the appropriate payload. We also returned a state light that determines whether light or dark theme is selected by the user. We’ll use this in our components.

We also need to add it to our root reducer and store. Here is the complete code for our 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));

Since we needed to persist the theme when the user refreshes, we had to get it from the local storage using localStorage.getItem() and pass it to our initial state.

Adding The Functionality To Our React Application

Styled components provide us with ThemeProvider that allows us to pass themes through our application. We can modify our App.js file to add this functionality.

Let’s take a look at it.

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 (
    
      
        ...
      
    
  );
}
export default App;

By passing themes through the ThemeProviderwe can easily use the theme props in our styles.

For instance, we can set the color to our bodyText custom color as follows.

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

We can use the custom themes anywhere we need color in our application.

For example, to define border-bottomwe do the following.

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

Conclusion

We began by delving into Sanity.io, setting it up and connecting it to our React application. Then we set up Redux and used the GROQ language to query our API. We saw how to connect and use Redux to our React app using react-reduxuse styled-components and theming.

However, we only scratched the surface on what is possible with these technologies. I encourage you to go through the code samples in my GitHub repo and try your hands on a completely different project using these technologies to learn and master them.

Resources

Smashing Editorial" width="35" height="46" loading="lazy" decoding="async(ks, vf, yk, il)




Source link