Créer une application Web avec React, Redux et Sanity.io
   
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.
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.
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  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 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-componentsHaving 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.jsfile).
- 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
 (ks, vf, yk, il)
(ks, vf, yk, il)Source link

