Networking

המטרה בחלק הזה היא להבין איך לקבל ולהכניס מידע, לטפל בשגיאות ולהציג נתונים. כדי שיהיה לנו מידע להשתמש בו, נשתמש ב-json server. הוא בחינם ויש בו מידע לדוגמא שאפשר להשתמש בו.

הכתובת שנשתמש בה לשליפת נתונים היא:

https://jsonplaceholder.typicode.com/posts?_limit=10

GET Requests

פונקציית fetchData תשלוף את הנתונים על ידי פקודת fetch ואחר כך נהפוך את מה שקבלנו ל-json.

const fetchData = async () => {
    const response = await fetch(
      `https://jsonplaceholder.typicode.com/posts?_limit=10`
    );

    const data = await response.json();
}

כדי להשתמש במידע נשמור אותו על ידי useState לתוך משתנה.

const [postList, setPostList] = useState([]);

const fetchData = async (limit = 10) => {
  const response = await fetch(
    `https://jsonplaceholder.typicode.com/posts?_limit=${limit}`
  );

  const data = await response.json();
  setPostList(data);
}

אנחנו רוצים לקרוא ל-fetchData כשהקומפוננטה עולה. נקרא ל-useEffect כדי לעשות את זה.

useEffect(() => {
    fetchData();
}, [])

כדי להציג את הרשימה נשתמש ב-FlatList.

import { Text, SafeAreaView, View, StyleSheet, 
  FlatList } from "react-native";
import { useState, useEffect } from "react";

export default function Index() {
  const [postList, setPostList] = useState<any[]>([])

  const fetchData = async (limit = 10) => {
    const response = await fetch(
      `https://jsonplaceholder.typicode.com/posts?_limit=${limit}`
    );

    const data = await response.json();
    setPostList(data);
  }

  useEffect(() => {
    fetchData();
  }, [])

  return (
    <SafeAreaView style={styles.mainContainer}>
      <View style={styles.listContainer}>
        <FlatList 
          data={postList}
          renderItem = {({ item }) => {
            return(
              <View style={styles.card}>
                <Text style={styles.titleText}>{item.title}</Text>
                <Text style={styles.bodyText}>{item.body}</Text>
              </View>
            )
          }}
          ListEmptyComponent={<Text>No Posts Found</Text>}
          ListHeaderComponent={<Text style={styles.headerText}>Post List</Text>}
        />
      </View>
    </SafeAreaView>
  );
}

const styles = StyleSheet.create({
  mainContainer: {
    flex: 1,
    backgroundColor: "#f5f5f5",
    paddingTop: 25
  },

  listContainer:{
    flex: 1,
    paddingHorizontal: 16
  },

  card: {
    backgroundColor: "#FFFFFF",
    padding: 16,
    borderRadius: 8,
    borderWidth: 1,
    marginBottom: 10
  },

  titleText: {
    fontSize: 22,
  },

  bodyText: {
    fontSize: 16,
    color: "#666666",
  },

  headerText: {
    fontSize: 26,
    textAlign: 'center',
    marginBottom: 12
  }
})

Loading State

נראה איך להציג למשתמש שהמידע נטען. נשתמש במצב של isLoading כשהערך ההתחלתי הוא true מכיוון שהמידע נטען מייד בכניסה לדף. בסיום הטעינה נעדכן את הערך שלו ל-false. בהתאם לערך של isLoading נציג ספינר טעינה.

export default function Index() {
...
  const [isLoading, setIsLoading] = useState(true);

  const fetchData = async (limit = 10) => {
    const response = await fetch(
      `https://jsonplaceholder.typicode.com/posts?_limit=${limit}`
    );

    const data = await response.json();
    setPostList(data);
    setIsLoading(false);
  }

  if(isLoading){
    return(
      <SafeAreaView style={styles.loadingContainer}>
        <ActivityIndicator size="large" color="#000" />
        <Text>Loading...</Text>
      </SafeAreaView>
    )
  }
  ...
  return(
    ...
  )
}
loadingContainer: {
    flex: 1,
    backgroundColor: "#f5f5f5",
    paddingTop: 25,
    justifyContent: 'center',
    alignItems: 'center'
}

Pull to Refresh

נשתמש בפונקציונליות של רענון המידע בעמוד על ידי משיכת העמוד מבלי צורך לטעון את כל האפליקציה מחדש.

קודם ניצור את המשתנה שיעקוב אחרי רענון המידע:

const [refreshing, setRefreshing] = useState(false);

נצמיד את ערך המשתנה לערך הרענון של הרשימה שלנו. נוסיף את פעולת onRefresh שמצפה לקבל פונציה שאומרת מה לעשות במקרה של רענון.

בפונקציה נשנה את הערך של משתנה refreshing ל-trueת נטען את הרשימה שוב ונחזיר את הערך ל-false.

export default function Index() {
  const [postList, setPostList] = useState<any[]>([])
  const [isLoading, setIsLoading] = useState(true);
  const [refreshing, setRefreshing] = useState(false);

  const fetchData = async (limit = 10) => {
    ...
  }

  const handleRefresh = () => {
    setRefreshing(true);
    fetchData(20);
    setRefreshing(false);
  }

  ...

  return (
    <SafeAreaView style={styles.mainContainer}>
      <View style={styles.listContainer}>
        <FlatList 
          data={postList}
          renderItem = {({ item }) => {
            return(
              <View style={styles.card}>
                <Text style={styles.titleText}>{item.title}</Text>
                <Text style={styles.bodyText}>{item.body}</Text>
              </View>
            )
          }}
          ListEmptyComponent={<Text>No Posts Found</Text>}
          ListHeaderComponent={<Text style={styles.headerText}>Post List</Text>}
          refreshing={refreshing}
          onRefresh={handleRefresh}
        />
      </View>
    </SafeAreaView>
  );
}

POST Request

נשתמש ב-Json Api של קודם על מנת לבצע פעולת post לכותרת ותוכן של פוסט.

ניצור משתנים שיקלטו את הכותרת והתוכן החדשים. ניצור גם משתנה שיעקוב אחרי פעולת ה-submit.

const [postTitle, setPostTitle] = useState("");
const [postBody, setPostBody] = useState("");
const [isPosting, setIsPosting] = useState(false);

נחבר את המשתנים האלה לשדות בטופס. נשתמש באותו הדף שהשתמשנו בו עד עכשיו. נשתמש ב-react fragment על מנת להחזיר קומפוננטה נוספת.

נוסיף 2 שדות קלט וכפתור שיבצע את פעולת ה-submit.

return (
  <SafeAreaView style={styles.mainContainer}>
    <>
    <View style={styles.inputContainer}>
      <TextInput
        style={styles.input}
        placeholder="Post Title"
        value={postTitle}
        onChangeText={setPostTitle}
      />
      <TextInput
        style={styles.input}
        placeholder="Post Body"
        value={postBody}
        onChangeText={setPostBody}
      />
      <Button
        title={isPosting ? "Adding..." : "Add Post"}
        onPress={addPost}
        disabled={isPosting}
      />
    </View>
  ...
  </>
  ...
  inputContainer: {
    backgroundColor: "#FFFFFF",
    padding: 16,
    borderRadius: 8,
    borderWidth: 1,
    margin: 16,
  },
  
  input: {
    height: 40,
    borderColor: "gray",
    borderWidth: 1,
    marginBottom: 8,
    padding: 8,
    borderRadius: 8,
  },

נגדיר את addPost שמטפלת בשליחת הטופס. נפנה ל-API עם פעולת post ונשלח את הנתונים. נקבל בחזרה את ה-post שיצרנו ונוסיף אותו לרשימת הפוסטים הקיימת.

const addPost = async () => {
  setIsPosting(true);
  const response = await fetch(
    "https://jsonplaceholder.typicode.com/posts",
    {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        title: postTitle,
        body: postBody,
      }),
    }
  );

  const newPost = await response.json();
  setPostList([newPost, ...postList]);
  setPostTitle("");
  setPostBody("");
  setIsPosting(false);
}

Error Handling

נטפל בשגיאות שמגיעות לאחר פעולות מול DB. נגדיר משתנה שיאחסן את השגיאה.

const [error, setError] = useState("");

נוסיף בלוקים של try-catch על מנת לתפוס את השגיאות שמגיעות.

זה הקוד המלא של הדוגמא:

import { Text, SafeAreaView, View, StyleSheet, 
  FlatList, ActivityIndicator, TextInput, Button } from "react-native";
import { useState, useEffect } from "react";

export default function Index() {
  const [postList, setPostList] = useState<any[]>([])
  const [isLoading, setIsLoading] = useState(true);
  const [refreshing, setRefreshing] = useState(false);
  const [postTitle, setPostTitle] = useState("");
  const [postBody, setPostBody] = useState("");
  const [isPosting, setIsPosting] = useState(false);
  const [error, setError] = useState("");

  const fetchData = async (limit = 10) => {
    try{
      const response = await fetch(
        `https://jsonplaceholder.typicode.com/posts?_limit=${limit}`
      );

      const data = await response.json();
      setPostList(data);
      setIsLoading(false);
    } catch(error){
      console.error("Error fetching data:", error);
      setIsLoading(false);
      setError("Failed to fetch post list.");
    }
  }

  const handleRefresh = () => {
    setRefreshing(true);
    fetchData(20);
    setRefreshing(false);
  }

  const addPost = async () => {
    setIsPosting(true);

    try {
      const response = await fetch(
        "https://jsonplaceholder.typicode.com/posts",
        {
          method: "POST",
          headers: {
            "Content-Type": "application/json",
          },
          body: JSON.stringify({
            title: postTitle,
            body: postBody,
          }),
        }
      );
      const newPost = await response.json();
      setPostList([newPost, ...postList]);
      setPostTitle("");
      setPostBody("");
      setError("");
    } catch (error) {
      console.error("Error adding new post:", error);
      setError("Failed to add new post.");
    }

    setIsPosting(false);
  }

  useEffect(() => {
    fetchData();
  }, [])

  if(isLoading){
    return(
      <SafeAreaView style={styles.loadingContainer}>
        <ActivityIndicator size="large" color="#000" />
        <Text>Loading...</Text>
      </SafeAreaView>
    )
  }

  return (
    <SafeAreaView style={styles.mainContainer}>
    { error ? (
        <View style={styles.errorContainer}>
          <Text style={styles.errorText}>{error}</Text>
        </View>
      ) : (
      <>
      <View style={styles.inputContainer}>
        <TextInput
          style={styles.input}
          placeholder="Post Title"
          value={postTitle}
          onChangeText={setPostTitle}
        />
        <TextInput
          style={styles.input}
          placeholder="Post Body"
          value={postBody}
          onChangeText={setPostBody}
        />
        <Button
          title={isPosting ? "Adding..." : "Add Post"}
          onPress={addPost}
          disabled={isPosting}
        />
      </View>

      <View style={styles.listContainer}>
        <FlatList 
          data={postList}
          renderItem = {({ item }) => {
            return(
              <View style={styles.card}>
                <Text style={styles.titleText}>{item.title}</Text>
                <Text style={styles.bodyText}>{item.body}</Text>
              </View>
            )
          }}
          ListEmptyComponent={<Text>No Posts Found</Text>}
          ListHeaderComponent={<Text style={styles.headerText}>Post List</Text>}
          refreshing={refreshing}
          onRefresh={handleRefresh}
        />
      </View>
      </>
    )}
    </SafeAreaView>
  );
}

const styles = StyleSheet.create({
  mainContainer: {
    flex: 1,
    backgroundColor: "#f5f5f5",
    paddingTop: 25
  },

  listContainer:{
    flex: 1,
    paddingHorizontal: 16
  },

  card: {
    backgroundColor: "#FFFFFF",
    padding: 16,
    borderRadius: 8,
    borderWidth: 1,
    marginBottom: 10
  },

  titleText: {
    fontSize: 22,
  },

  bodyText: {
    fontSize: 16,
    color: "#666666",
  },

  headerText: {
    fontSize: 26,
    textAlign: 'center',
    marginBottom: 12
  },

  loadingContainer: {
    flex: 1,
    backgroundColor: "#f5f5f5",
    paddingTop: 25,
    justifyContent: 'center',
    alignItems: 'center'
  },

  inputContainer: {
    backgroundColor: "#FFFFFF",
    padding: 16,
    borderRadius: 8,
    borderWidth: 1,
    margin: 16,
  },

  input: {
    height: 40,
    borderColor: "gray",
    borderWidth: 1,
    marginBottom: 8,
    padding: 8,
    borderRadius: 8,
  },

  errorContainer: {
    backgroundColor: "#FFC0CB",
    padding: 16,
    borderRadius: 8,
    borderWidth: 1,
    margin: 16,
    alignItems: "center",
  },

  errorText: {
    color: "#D8000C",
    fontSize: 16,
    textAlign: "center",
  },
})

ניווט במאמר

מאמרים אחרונים

Weekly Tutorial