การจัดการ State ของข้อมูลที่ได้จาก API ใน React Native
หลังจากที่เราได้เรียนรู้วิธีการดึงข้อมูลจาก API ด้วย Fetch API ใน React Native กันไปแล้วในบทที่แล้ว (อย่าลืมว่าเราเน้นการใช้ GET method เพื่อดึงข้อมูลอย่างเดียวนะครับ) ในบทนี้เราจะมาเรียนรู้ถึงวิธีการจัดการข้อมูลเหล่านั้น หรือที่เรียกว่าการจัดการ State นั่นเองครับ เพราะการดึงข้อมูลมาอย่างเดียวไม่พอ เราต้องนำข้อมูลเหล่านั้นมาแสดงผลและอัพเดทใน UI ของแอปพลิเคชันเราด้วย
ใน React Native เรามีเครื่องมือหลักๆ สองตัวที่นิยมใช้ในการจัดการ State ได้แก่ useState Hook และ Context API ซึ่งแต่ละตัวก็มีข้อดีข้อเสียแตกต่างกันไป เราจะมาดูกันว่าแต่ละตัวทำงานอย่างไร และควรเลือกใช้ตัวไหนในสถานการณ์ใด
useState Hook: จัดการ State อย่างง่ายดายใน Component
useState Hook เป็น Hook ที่มาพร้อมกับ React ตั้งแต่เวอร์ชัน 16.8 เป็นต้นมา (React Native ก็ใช้ React เป็นพื้นฐาน ดังนั้นจึงใช้ได้แน่นอน) มันเป็นเครื่องมือที่ง่ายและสะดวกในการจัดการ State ภายใน Component เดียว ถ้าข้อมูลที่เราดึงมาจาก API จะถูกใช้แค่ใน Component นั้น และไม่ได้ซับซ้อนอะไรมาก useState คือตัวเลือกที่ดีที่สุดครับ
หลักการทำงานของ useState:
useState จะคืนค่ากลับมาเป็น Array ที่มีสองสมาชิก:
- สมาชิกตัวแรก: ค่าของ State ปัจจุบัน (initial state ตอนเริ่มโปรแกรม หรือค่าที่ถูกอัพเดทล่าสุด)
- สมาชิกตัวที่สอง: ฟังก์ชันสำหรับอัพเดทค่าของ State
ตัวอย่างการใช้งาน useState:
import React, { useState, useEffect } from 'react';
import { View, Text, FlatList, StyleSheet } from 'react-native';
const App = () => {
const [data, setData] = useState([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
const fetchData = async () => {
try {
const response = await fetch('https://jsonplaceholder.typicode.com/todos'); // API สำหรับทดสอบ
if (!response.ok) {
throw new Error('Network response was not ok');
}
const json = await response.json();
setData(json);
} catch (error) {
console.error('Error fetching data:', error);
} finally {
setLoading(false); // ไม่ว่าจะสำเร็จหรือล้มเหลว ให้หยุด Loading
}
};
fetchData();
}, []); // [] หมายถึง useEffect จะทำงานแค่ครั้งแรกที่ Component ถูก Mount เท่านั้น
if (loading) {
return (
<View style={styles.container}>
<Text>กำลังโหลดข้อมูล...</Text>
</View>
);
}
return (
<View style={styles.container}>
<FlatList
data={data}
keyExtractor={(item) => item.id.toString()} // Convert id to string for key
renderItem={({ item }) => (
<View style={styles.item}>
<Text>{item.title}</Text>
<Text>Completed: {item.completed ? 'Yes' : 'No'}</Text>
</View>
)}
/>
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
padding: 20,
},
item: {
marginBottom: 10,
padding: 10,
borderWidth: 1,
borderColor: '#ccc',
},
});
export default App;
คำอธิบายโค้ด:
useState([]): เราสร้าง State ชื่อdataให้มีค่าเริ่มต้นเป็น Array ว่างเปล่า ([]) เพราะเราคาดหวังว่า API จะส่งข้อมูลกลับมาเป็น ArrayuseState(true): เราสร้าง State ชื่อloadingให้มีค่าเริ่มต้นเป็นtrueเพื่อบอกว่าแอปพลิเคชันกำลังโหลดข้อมูลอยู่useEffect: Hook นี้จะทำงานหลังจาก Component ถูก Render ครั้งแรก เราใช้มันเพื่อเรียกฟังก์ชันfetchDataเพื่อดึงข้อมูลจาก APIsetData(json): เมื่อดึงข้อมูลสำเร็จ เราใช้ฟังก์ชันsetDataเพื่ออัพเดทค่าของ Statedataด้วยข้อมูลที่ได้จาก APIsetLoading(false): เมื่อดึงข้อมูลเสร็จ (ไม่ว่าจะสำเร็จหรือล้มเหลว) เราใช้ฟังก์ชันsetLoadingเพื่ออัพเดทค่าของ Stateloadingเป็นfalseเพื่อบอกว่าการโหลดข้อมูลเสร็จสิ้นแล้วFlatList: Component นี้ใช้สำหรับแสดงรายการข้อมูลยาวๆ อย่างมีประสิทธิภาพkeyExtractor: กำหนด property ที่ใช้เป็น key สำหรับแต่ละ item ใน FlatList เพื่อให้ React สามารถ update component ได้อย่างมีประสิทธิภาพ
ข้อควรระวังในการใช้ useState:
- อย่าแก้ไข State โดยตรง: ห้ามใช้
data.push()หรือdata[0] = 'something'เพราะ React จะไม่รู้ว่า State มีการเปลี่ยนแปลง และ UI จะไม่อัพเดท ให้ใช้ฟังก์ชันsetDataเสมอ - State เป็น Immutable: เมื่ออัพเดท State ที่เป็น Object หรือ Array ควรสร้างสำเนา (copy) ของ Object หรือ Array นั้นก่อน แล้วค่อยแก้ไขสำเนา จากนั้นค่อยส่งสำเนาไปอัพเดท State เช่น
setData([...data, newItem])(ใช้ Spread Operator)
Context API: แชร์ State ข้าม Component
Context API เป็นเครื่องมือที่ช่วยให้เราสามารถแชร์ State ระหว่าง Component ต่างๆ ได้ง่ายขึ้น โดยไม่ต้องส่ง props ผ่าน Component หลายชั้น (prop drilling) ซึ่งเป็นปัญหาที่พบบ่อยใน React Native
หลักการทำงานของ Context API:
- สร้าง Context: สร้าง Context Object ด้วย
React.createContext() - Provider: ห่อ Component ที่ต้องการแชร์ State ด้วย
Context.Providerและกำหนดค่าvalueที่ต้องการแชร์ - Consumer: ใช้
useContext Hookใน Component ที่ต้องการเข้าถึง State
ตัวอย่างการใช้งาน Context API:
import React, { useState, useEffect, createContext, useContext } from 'react';
import { View, Text, FlatList, StyleSheet } from 'react-native';
// 1. สร้าง Context
const DataContext = createContext();
const App = () => {
const [data, setData] = useState([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
const fetchData = async () => {
try {
const response = await fetch('https://jsonplaceholder.typicode.com/todos'); // API สำหรับทดสอบ
if (!response.ok) {
throw new Error('Network response was not ok');
}
const json = await response.json();
setData(json);
} catch (error) {
console.error('Error fetching data:', error);
} finally {
setLoading(false);
}
};
fetchData();
}, []);
// 2. Provider: ส่ง State ผ่าน DataContext.Provider
return (
<DataContext.Provider value={{ data, loading }}>
<View style={styles.container}>
<TodoList />
</View>
</DataContext.Provider>
);
};
const TodoList = () => {
// 3. Consumer: ใช้ useContext เพื่อเข้าถึง State
const { data, loading } = useContext(DataContext);
if (loading) {
return <Text>กำลังโหลดข้อมูล...</Text>;
}
return (
<FlatList
data={data}
keyExtractor={(item) => item.id.toString()}
renderItem={({ item }) => (
<View style={styles.item}>
<Text>{item.title}</Text>
<Text>Completed: {item.completed ? 'Yes' : 'No'}</Text>
</View>
)}
/>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
padding: 20,
},
item: {
marginBottom: 10,
padding: 10,
borderWidth: 1,
borderColor: '#ccc',
},
});
export default App;
คำอธิบายโค้ด:
createContext(): เราสร้าง Context Object ชื่อDataContextDataContext.Provider: เราห่อ ComponentTodoListด้วยDataContext.Providerและกำหนดค่าvalueเป็น Object ที่มี StatedataและloadinguseContext(DataContext): ใน ComponentTodoListเราใช้useContext(DataContext)เพื่อเข้าถึงค่า State ที่ถูกแชร์
เมื่อไหร่ควรใช้ Context API?
- เมื่อต้องการแชร์ State ข้าม Component หลายๆ ตัว โดยไม่ต้องส่ง props ผ่าน Component หลายชั้น
- เมื่อ State มีความซับซ้อน และมีการเปลี่ยนแปลงบ่อย
- เมื่อต้องการแยก Logic การจัดการ State ออกจาก Component
ข้อควรระวังในการใช้ Context API:
- ถ้า State ที่แชร์มีการเปลี่ยนแปลงบ่อยๆ อาจทำให้ Component ที่ใช้ Context ถูก re-render บ่อยเกินไป
- สำหรับ State ที่ไม่ซับซ้อน และใช้แค่ใน Component เดียว
useStateอาจเป็นทางเลือกที่ดีกว่า
การอัพเดท State: ข้อควรจำ
ไม่ว่าเราจะใช้ useState หรือ Context API สิ่งที่สำคัญที่สุดคือการอัพเดท State อย่างถูกต้อง เพื่อให้ React Native สามารถ Render UI ได้อย่างถูกต้องและมีประสิทธิภาพ
หลักการสำคัญในการอัพเดท State:
- Immutability: State ใน React Native เป็น Immutable นั่นหมายความว่าเราไม่ควรแก้ไข State โดยตรง แต่ควรสร้างสำเนา (copy) ของ State แล้วค่อยแก้ไขสำเนา จากนั้นค่อยส่งสำเนาไปอัพเดท State
- Functional Updates: เมื่ออัพเดท State ที่ขึ้นอยู่กับค่า State ก่อนหน้า เราควรใช้ Functional Updates เพื่อให้แน่ใจว่าเราใช้ค่า State ที่ถูกต้องที่สุด เช่น
setData(prevData => [...prevData, newItem])
ในบทนี้เราได้เรียนรู้เกี่ยวกับการจัดการ State ของข้อมูลที่ได้จาก API ด้วย useState Hook และ Context API ซึ่งเป็นเครื่องมือที่สำคัญในการพัฒนา React Native application อย่าลืมฝึกฝนและนำไปประยุกต์ใช้กับโปรเจกต์ของคุณนะครับ ในบทต่อไป เราจะมาเรียนรู้เกี่ยวกับการใช้ Axios Library ซึ่งเป็นอีกทางเลือกหนึ่งในการเชื่อมต่อ API ที่ได้รับความนิยมเช่นกัน แล้วเจอกันครับ!