Aller au contenu principal


Réact-Native : cRuD 1/2

Ce billet analyse un exemple de code "React-Native" pour gérer les opérations Create, Read, Update et Delete sur une table d'une base de données stockée sur un serveur distant. Ce code est fourni dans l'onglet "crud.js" qui figure au bas de cette page.

Les opérations Read et Delete sont décrites dans cette page,  les opérations Create et Update le sont en cliquant ce ce lien.

 

 

Billet créé le :
23 juin 2022

Les fonctions ReadRecords(), InsertRecord(), DeleteRecord() de ce code utilisent l'API fetch pour respectivement lire, insérer ou effacer des enregistrements de la table. L'URL, argument obligatoire de fetch  est un script php détaillé ICI.

Il peut être utile de (re) lire le billet sur l'utilisation de "useState" et celui sur la navigation pour comprendre l'appel des fonctions "Crud" et "UpdateCrud".

1. Le coeur :

La fonction-composant-écran "Crud" est appelée pour afficher la page d'accueil de notre navigation. Elle pourra donc utiliser les props "navigation" et "route". 
Lire le billet "utiliser les props" pour une explication des props.
Lire également le billet "navigation entre écrans".

Elle définit de plus des variables (internes) et de leur "setter" :

  • dataBd  pour mémoriser les données lues dans la BdD ;

  • studentName pour mémoriser le nom d'étudiant par exemple lors de sa saisie par l'utilisateur ;

  • course pour mémoriser le cours suivi par exemple lors de sa saisie par l'utilisateur.

Cliquer pour agrandir l'image

  • La fonction "ReadRecords()" (détaillée au §2) effectue une lecture de la Base de Données et stocke les enregistrements de la base de données dans la variable dataBd ;

  • La  fonction Lire() (détaillée au §3) affiche une vue  des enregistrements de la BdD   comme l'illustre l'image jointe ;

  • La fonction Delete() (détaillée au §4) supprime un enregistrement de la BdD ;

  • Les fonctions Inscrire() et Update() affiche une nouvelle vue permettant la saisie d'un nouvel enregistrement ou la mise à jour d'un enregistrement existant comme l'expose le billet "React-native : CrUd 2/2".

  • les fonctions afficherItems() et getItemLayout() sont utilisés par le composant "FlatList" comme le détaille le billet "FlatList et navigation"

  • la fonction press() gère les actions des utilisateurs sur les boutons  présents dans la vue  (cf. le billet "FlatList et navigation").

2. La fonction ReadRecords()

Cette fonction utilise l'API fetch avec pour arguments :

  • l'URL du script de gestion de la BdD distante (voir ici sa description) dans la variable CrudAPIURL qui vaut "https://code.dhumbert.info/mycrud.php" ;

  • les réglages de l'URL dans le "tableau"
        {   
            method: 'POST',
            headers: Headers,
            body: JSON.stringify(Data),
        }

Comme indiqué dans le script "mycrud.php", pour une lecture de la  BdD,  le seul paramètre nécessaire à transmettre est "op". Il doit avoir "read" pour valeur, cela explique le contenu de la variable "Data" qui constitue le corps (body) de la requête.

L'entête (headers) spécifie que le corps doit être émis au format JSON d'où l'utilisation de la fonction JSON.stringify sur "Data"

La requête est transmise en mode POST (pour que les paramètres ne figurent pas dans l'URL).

NB : si cette requête était transmise en mode GET, la forme de l'URL aurait été  "mycrud.php?op=read".

Les méthodes then et catch sont appliquées sur la réponse reçue.

D'après la ligne $response = array("Message"=>$msg, "Data"=>$data) du  fichier mycrud.php, les enregistrements de la BdD figurent dans la clé "Data" du "tableau" $response fourni comme résultat. C'est pourquoi, le setter setData() est appelé avec le paramètre response.Data pour mémoriser ces données dans la variable dataBd de la fonction Crud() lorsqu'il y a des données à gérer.

La variable dataBd est donc une "liste d'items" que l'on peut se représenter ainsi :

[
         {num :40, name :"Pierre", course :"informatique"}
         {num :48, name :"Marie", course :"informatique"}
          ....
]

Chaque item de la liste dispose donc des clés "num", "name" et "course" permettant l'accès aux données.

3. La fonction Lire()

Au cœur de cette fonction se trouve le composant "FlatList" dont l'objet est l'affichage dans une liste déroulante des items de la "liste" dataBd initialisée par ReadRecords(). Un exemple d'utilisation du composant "FlatList"  est détaillé dans la page "FlatList et navigation".

 Comme l'indique la fonction afficherItems(), le rendu d'une ligne est constituée de :

  • une icône-bouton pour la mise à jour avec appel à la fonction Update() ayant pour paramètres la valeur des clés num, name et course de l'item affiché ;

  • une icône-bouton pour la suppression avec appel à la fonction Delete() ayant pour paramètre la valeur de la clé "num" de l'item affiché ;

  • la valeur de la clé "name" de l'item affiché ;

  • la valeur de la clé  "course" de l'item affiché ;

  • le rang de l'item affiché (valeur du paramètre "index".

Cette fonction est gérée (cf. "FlatList et navigation") par le composant "FlatList". Son code :

    const afficherItems = ({ item, index }) => (
        <View  style={styles.affichageItem}>
            <TouchableHighlight
                style={styles.u}
                onPress= {Update(item.num, item.name, item.course)}
                underlayColor="aliceblue"
            >
                <Image
                    source={require('./ico/config.png')}
                />
            </TouchableHighlight>
            <TouchableHighlight
                style={styles.u}
                onPress={Delete(item.num)}
                underlayColor="aliceblue"
            >
                <Image
                    source={require('./ico/cut.png')}
                />
            </TouchableHighlight>
            <View style={styles.rView}>
                <Text style={[styles.rText,{width:"33%"}]} >{item.name}</Text>
                <Text style={[styles.rText,{width:"33%"}]} >{item.course}</Text>
            </View>
            <Text style ={[styles.rText,{alignSelf:"center"}]}> {index + 1 }   </Text>
        </View>
    );

 

A la place du composant FlatList, il est possible d'utiliser le composant ScrollView

exemple de code pour le composant ScrollView en cliquant ici

<ScrollView>
    <View >                    
         data.map(({ name, course, num }) => (
            <View key={num} style={{flexDirection:"row", alignContent:"space-between"}}>
                <TouchableHighlight
                    style={styles.u}
                    onPress= {Update(num, name, course)}
                    underlayColor="aliceblue"
                >
                    <Image
                        source={require('./ico/config.png')}
                    />     
                </TouchableHighlight>
                <TouchableHighlight
                    style={styles.u}
                    onPress={Delete(num)}
                    underlayColor="aliceblue"
                >
                    <Image
                        source={require('./ico/cut.png')}
                    />
                </TouchableHighlight>
                <View style={{width:"90%",flexDirection:"row", justifyContent:"space-evenly",alignItem:"center",alignContent:"space-around", paddingVertical:10}}>
                    <Text>{name}</Text>
                    <Text>{course}</Text>
                    <Text>{num}</Text>
                </View>
            </View>
          ))   
    </View>
</ScrollView>;

Re(lire) ce billet pour voir une utilisation expliquée de la fonction JS map().

4. La fonction Delete()

Cette fonction utilise l'API fetch avec pour arguments :

  • l' URL du script de gestion de la BdD distante (voir ici sa description) : CrudAPIURL qui vaut "https://code.dhumbert.info/mycrud.php  :

  • le tableau
        {   
            method: 'POST',
            headers: Headers,
            body: JSON.stringify(Data),
        }

Pour une requête de suppression dans la BdD,  les paramètres qui doivent être transmis sont :

  • "op"  avec "delete" pour valeur  ;

  • "num" avec le numéro de l'enregistrement à supprimer,  numéro reçu en paramètre de la fonction delete().

Lorsque la suppression est effectuée, la fonction ReadRecords() est appelée pour mettre à jour la variable dataDb. Ce nouveau contenu sera affiché grâce à la fonction Lire() comme expliqué ci-dessus.

5. Les fonctions Inscrire() et UpDate()

Ces fonctions utilisent la fonction anonyme :

function () => {   
           navigation.navigate('UpdateCrud',{
                  ........
            })
        };

qui appelle l'écran "UpdateCrud" en lui transmettant un "objet" ayant pour clés "num", "name", "course" (voir la page CrUd 2/2 ). Les valeurs de ces clés dépendent de l'opération à effectuer sur la base de données. 

Dans ce code,

  • num : 0 correspond à une demande de  création d'un nouvel enregistrement

    • name et course sont vides.

  • num : arg1 (=/= 0) correspond à une demande de mise à jour de l'enregistrement arg1 

    • name et course sont alors les données de l'enregistrement arg1 à mettre jour.

 

 

Cliquez sur le bouton pour copier le code dans le presse papier
import React, { useState, useRef } from "react";
import { Alert,
StyleSheet,
TextInput,
Text,
Button,
TouchableHighlight,
Image,
   FlatList,
Dimensions,
View } from "react-native";
//
const SCREEN_HEIGHT = Dimensions.get("screen").height;
const SCREEN_WIDTH = Dimensions.get("screen").width;
// penser à retirer la hauteur du titre de l'espace disponible
const PAGE_HEIGHT = SCREEN_HEIGHT - 200;
const NAV_HEIGHT = Math.trunc (PAGE_HEIGHT / 5);;
const CONTENT_HEIGHT = PAGE_HEIGHT - NAV_HEIGHT * 2 ;
const NB_ITEM = 9;
const ITEM_HEIGHT = Math.trunc (CONTENT_HEIGHT / NB_ITEM);
//
//const NB_ITEM = Math.trunc (SCREEN_HEIGHT / (2 * ITEM_HEIGHT ));
console.log ( "NB_ITEM : "+ NB_ITEM);
//
const getItemLayout = ( data, index) => {
return {
       length: ITEM_HEIGHT,
       offset: ITEM_HEIGHT * (index ),
       index,
};
};
//
const Crud =( {navigation} ) =>{
   //
   const [ dataBd, setDataBd ]=useState("");
const [ studentName, setStudentName ]=useState("");
const [ course, setCourse ]=useState("");
   //
   const list = useRef(null);
   //
const ReadRecords =() => {
var CrudAPIURL="https://code.dhumbert.info/mycrud.php";
var Headers={
'Accept':'Application/JSON',
'Content-Type':'Application/JSON'
};
var Data={
"op":"read"
};
fetch(
CrudAPIURL,
{
method: 'POST',
headers: Headers,
body: JSON.stringify(Data),
})
.then( (response) => response.json())
.then( (response) =>
{
/*alert(response.Data[1].num);*/
var i = response.Data.length;
if (i > 0) {
/*console.log(response.Data[i-1].num);*/
setDataBd ( response.Data );
}
else {
setDataBd("");
Alert.alert(
//title
'Attention',
//body
"Aucun enregistrement trouvé dans la table",
[
{
text: 'Yes',
onPress: () => console.log('Yes Pressed')
},
],
{cancelable: false},
//clicking out side of alert will not cancel
);
console.error("Aucun enregistrement trouvé");
}
})
.catch( (error) =>
{
alert("error"+error);
console.error(error);
})
}
   //
   // affichage des items de la FlatList
   //https://blog.logrocket.com/deep-dive-react-native-flatlist/
   const afficherItems = ({ item, index }) => (
       <View style={styles.affichageItem}>
           <TouchableHighlight
               style={styles.u}
               onPress= {Update(item.num, item.name, item.course)}
               underlayColor="aliceblue"
           >
               <Image
                   source={require('./ico/config.png')}
               />
           </TouchableHighlight>
           <TouchableHighlight
               style={styles.u}
               onPress={Delete(item.num)}
               underlayColor="aliceblue"
           >
               <Image
                   source={require('./ico/cut.png')}
               />
           </TouchableHighlight>
           <View style={styles.rView}>
               <Text style={[styles.rText,{width:"33%"}]} >{item.name}</Text>
               <Text style={[styles.rText,{width:"33%"}]} >{item.course}</Text>
           </View>
           <Text style ={[styles.rText,{alignSelf:"center"}]}> {index + 1 } </Text>
       </View>
   );
   //
   // action des bouton de navigation top-middle et bottom
   const press = (rang) => {
    //list.current : le pointeur sur l'objet FlatList cf: useRef
       //scroll to rang
       list.current.scrollToIndex({ animated: true, index: rang});
   };
   //
   // Lecture dee la Bd e Affichage des données (dataBd)
   const Lire =() => {
       let nbEnr = 0;
       if (dataBd==""){   
           return(
               <View >
                   <Text>
                       Il n'y a aucun enregistrement dans la table
                   </Text>
               </View>
           )
       }
       else{
       nbEnr = dataBd.length;
       return(
           <View style={styles.containerLire}>
               <View>
                   <Text style={{ textAlign:"center", fontWeight :"bold",fontSize:20 }}>
                       - Lecture de la table -
                   </Text>
                   <Text style ={[styles.rText,{alignSelf:"center",padding:10}]}>
                       il y a {nbEnr} enregistrements dans la table
                   </Text>
               </View>
               <View style={{height: NAV_HEIGHT}}>
                   <View style={{backgroundColor:"cornsilk"}}>
                       <Text >
                       </Text>
                       <Text style={styles.rText1} >
                           - faire défiler en glissant votre doigt.
                       </Text>
                       <Text style={styles.rText1} >
                           - appuyez sur <Image
                                    source={require('./ico/config.png')}
                                    />
                           {" "}pour mettre à jour l'enregistrement ...
                       </Text>
                       <Text style={styles.rText1} >
                           - appuyez sur <Image
                                           source={require('./ico/cut.png')}
                                        />
                            {" "}pour le supprimer.
                       </Text>
                       <Text style={styles.rText1} >
    - appuyez sur <Image                                               
                                               source={require('./ico/plus.png')}
         />
    {" "}pour ajouter un nouvel enregistrement.
</Text>
                       <Text >
                       </Text>
                   </View>
                   <View style={styles.containerListeEnr}>
                       <View >
                           {!dataBd && <Text> Chargement...</Text>}
                           {dataBd &&
                               <FlatList
                                   ref={list}
                                   data={ dataBd }
                                   renderItem={afficherItems}
                                   getItemLayout={getItemLayout}
                                   //initialScrollIndex={2}
                                   persistentScrollbar = {true}
                                   showsVerticalScrollIndicator={true}
                                   contentContainerStyle={styles.containerFlatList}
                               />
                           }
                       </View>
                   </View>
                   <View style={styles.containerButton}>
                       <TouchableHighlight
                           style={styles.u}
                           onPress={ReadRecords}
                           underlayColor="aliceblue"
                       >
                           <Text style={styles.u2}>
                               Actualiser
                           </Text>
                       </TouchableHighlight>
                       <TouchableHighlight
                               style={styles.u}
                               onPress= {() => press(0)}
                               underlayColor="aliceblue"
                           >
                           <Text style={styles.u2}>
                                Aff. les {NB_ITEM} premiers
                           </Text>                       
                       </TouchableHighlight>
                       <TouchableHighlight
                               style={styles.u}
                               onPress= {() => press(nbEnr - 1)}
                               underlayColor="aliceblue"
                           >
                           <Text style={styles.u2}>
                                Aff. les {NB_ITEM} derniers
                           </Text>                       
                       </TouchableHighlight>
                       <View style={{flexDirection : "row", alignItems:'center',justifyContent:"center"}}>
                           <View style={styles.u}>
                               <Text style={styles.u2}>
                                   Aff. l'enregistrement :
                                </Text>
                                <Text style={styles.u2}>
                                   nb : entre 1 et {nbEnr}
                                </Text>
                            </View>
                           <TextInput
                               style={styles.inputAffEnr}
                               placeholder="nb"
                               keyboardType="numeric"
                               //onChangeText={(text)=>{numEnr.current=text}}
                               onEndEditing={(e)=>(press(e.nativeEvent.text -1))}
                               //value={numEnr.current}
                               maxLength={4}
                           />
                           <TouchableHighlight
                               style={styles.u}
                               onPress= {Inscrire('5')}
                               underlayColor="aliceblue"
                           >
                               <Image
                                style= {{height:40, width:40, resizeMode: 'stretch'}}
                                   source={require('./ico/plus.png')}
                               />
                       </TouchableHighlight>
                       </View>
                   </View>
               </View>
           </View>       
       )}
   }
   //
   //
   const Inscrire = () => () => {
        navigation.navigate('UpdateCrud',{
         num: 0,
         name:"",
         course:"",
            });
    }   
   //
   const Update = (arg1, arg2, arg3 ) => () => {
       navigation.navigate('UpdateCrud',{
           num: arg1,
name: arg2,
           cours: arg3,
           });                       
   }
//   
   const Delete = (arg) => () => {
       /*alert("delete = " + arg);*/
       var CrudAPIURL="https://code.dhumbert.info/mycrud.php";
/*var APIURL="http://192.168.1.20/mycrud.php";*/
var Headers={
'Accept':'Application/JSON',
'Content-Type':'Application/JSON'
           };
var Data={
"op":"delete",
           num : arg,
};
           fetch(
               CrudAPIURL,
               {
                   method: 'POST',
                   headers: Headers,
                   body: JSON.stringify(Data),
               })
                   .then( (response) => response.json())
                   .then( (response) =>
                       {
                       //alert(response.Message);
                       Alert.alert(
                            //title
                            'Attention',
                            //body
                            response.Message,
                            [
                               {
                                text: 'Yes',
                                onPress: () => console.log('Yes Pressed')
                               },
                            ],
                            {cancelable: false},
                            //clicking out side of alert will not cancel
                           );
                       })
                   .catch( (error) =>
                       {
                           //alert("error"+error);
                           Alert.alert(
                                //title
                                'Attention',
                                //body
                                response.Message,
                                [
                                   {
                                    text: 'Yes',
                                    onPress: () => console.log('Yes Pressed')
                                   },
                                ],
                                {cancelable: false},
                                //clicking out side of alert will not cancel
                               );
                           console.error(error);
                       })           
       }   
       ReadRecords();
       return(
           <View style={styles.container}>
               { Lire()}
            </View>
       )
   }
//
const styles = StyleSheet.create({
container: {
       width:SCREEN_WIDTH,
       height : PAGE_HEIGHT ,
       backgroundColor: "lightcyan",
},
   containerListeEnr : {
       height : ITEM_HEIGHT * NB_ITEM ,
   },
containerButton : {
    flexDirection:"row",
    justifyContent:"center",
    alignItems :"center",
    flexWrap: "wrap",
    height : NAV_HEIGHT
    },
containerFlatList:{
    margin : 2,
    backgroundColor: "white",
    paddingBottom: 5
    },
affichageItem : {
    flexDirection:"row",
    alignContent:"space-around",
    height: ITEM_HEIGHT
    },
button: {
paddingHorizontal: 8,
paddingVertical: 10,
borderRadius: 8,
backgroundColor: "oldlace",
alignSelf: "flex-start",
marginHorizontal: "2%",
marginBottom: 6,
minWidth: "48%",
textAlign: "center",
},
u: {
paddingHorizontal: 8,
paddingVertical: 6,
borderRadius: 4,
backgroundColor: "dodgerblue",
alignSelf: "flex-start",
marginHorizontal: "1%",
marginBottom: 6,
   marginTop: 6,
minWidth: 40,
textAlign: "center",
   color: 'black',
fontWeight: 'bold',
},
u2: {
//paddingHorizontal: 8,
paddingVertical: 1,
borderRadius: 4,
backgroundColor: "dodgerblue",
alignSelf: "flex-start",
minWidth: 40,
textAlign: "center",
   color: 'black',
fontWeight: 'bold',
},
input: {
height: 40,
margin: 12,
borderWidth: 1,
padding: 10,
color: 'black',
fontWeight: 'bold',
},
rView: {
width:"65%",
flexDirection:"row",
justifyContent:"space-evenly",
alignItem:"center",
alignContent:"space-around",
paddingVertical:10,
},
rText: {
color: 'black',
fontWeight: 'bold',
},
rText1: {
color: 'black',
fontWeight: 'bold',
width:"95%",
},
   buttonAffEnrContainer:{
       flexDirection : "column",
       alignItems:'center',
       justifyContent:"center",
       backgroundColor: "dodgerblue"
   },
   inputAffEnr :{
    textAlign:"center",
    color:"black",
    fontSize:20,
    borderColor:"black",
    borderWidth:2,
    backgroundColor:'#CCD3FF',
    paddingHorizontal: 8,
    paddingVertical: 6,
borderRadius: 4,
    width: 100
   },
});
//
export default Crud;