Close Menu

    Subscribe to Updates

    Get the latest creative news from FooBar about art, design and business.

    What's Hot

    Task Management cho Cursor AI – Tăng 10x Hiệu Quả Coding

    Tháng 5 28, 2025

    Cài Đặt n8n Trên VPS Ubuntu – Hướng Dẫn Chi Tiết

    Tháng 5 28, 2025

    Context.7: Tool RAG Miễn Phí Cho AI Coding Assistant

    Tháng 5 28, 2025
    Facebook X (Twitter) Instagram
    • Demos
    • Buy Now
    Facebook X (Twitter) Instagram Pinterest Vimeo
    Minh SEOMinh SEO
    • Home
    • Về chúng tôi
    • Contact
    • AI Agent
    • Javascript
    • Quan điểm hay
    Subscribe
    Minh SEOMinh SEO
    Trang chủ » Hướng Dẫn Xây Dựng Ứng Dụng Đặt Đồ Ăn Full Stack với React Native
    Javascript

    Hướng Dẫn Xây Dựng Ứng Dụng Đặt Đồ Ăn Full Stack với React Native

    minhminhBy minhminhTháng 5 19, 2025Không có bình luận25 Mins Read
    Share Facebook Twitter Pinterest LinkedIn Tumblr Reddit Telegram Email
    Share
    Facebook Twitter LinkedIn Pinterest Email

    Dựa trên video hướng dẫn của NJ Developers

    Bạn đang muốn xây dựng một ứng dụng đặt đồ ăn hoàn chỉnh nhưng không biết bắt đầu từ đâu? Mình hiểu rõ thách thức đó và hôm nay chúng ta sẽ cùng nhau xây dựng một ứng dụng đặt pizza từ A-Z!

    Trong bài viết này, mình sẽ tóm tắt workshop từ NJ Developers về cách xây dựng ứng dụng đặt đồ ăn full stack sử dụng React Native và Supabase. Chúng ta sẽ đi từ ý tưởng, thiết kế UI, xây dựng backend, đến triển khai ứng dụng hoàn chỉnh.

    Nội Dung Chính

    • Giới Thiệu & Cài Đặt Môi Trường (00:00-06:20)
    • Khởi Tạo Dự Án Expo (06:21-17:45)
    • Làm Việc Với UI Component (17:46-24:30)
    • Tạo Component Tùy Chỉnh (24:31-30:15)
    • TypeScript & Type Safety (30:16-37:10)
    • Hiển Thị Danh Sách Với FlatList (37:11-43:30)
    • Thiết Lập Navigation & Route (43:31-59:45)
    • Màn Hình Chi Tiết Sản Phẩm (59:46-01:17:20)
    • Shopping Cart & React Context (01:17:21-01:43:30)
    • Phần Admin (01:43:31-01:53:40)
    • Tạo Sản Phẩm & Form (01:53:41-02:11:15)
    • Tích Hợp Supabase (02:11:16-02:26:30)
    • Authentication (02:26:31-02:45:45)
    • Database & CRUD Operations (02:45:46-03:23:10)
    • Tích Hợp Supabase Storage (03:23:11-03:32:40)
    • Real-time Updates (03:32:41-03:43:20)
    • Tích Hợp Thanh Toán Với Stripe (03:43:21-04:07:30)
    • Push Notifications (04:07:31-04:29:30)

    Giới Thiệu & Cài Đặt Môi Trường (00:00-06:20)

    Trong workshop này, chúng ta sẽ xây dựng một ứng dụng đặt đồ ăn hoàn chỉnh (kiểu Domino’s Pizza) sử dụng React Native và Supabase. Đây là hướng dẫn Zero to Hero, giúp bạn nâng cao kỹ năng từ cơ bản đến thành thạo việc xây dựng ứng dụng mobile full stack.

    Điểm Chính:

    • Tech stack bao gồm: React Native + Expo + Typescript + Supabase
    • Ứng dụng hoạt động được trên cả iOS và Android
    • Workshop được chia thành 3 module: UI, Backend và Features nâng cao
    • Bạn cần cài đặt NodeJS (phiên bản LTS) để bắt đầu
    • Công cụ tùy chọn: Git, VSCode, Expo Go app cho thiết bị di động

    Ý Kiến Của Mình:

    Việc sử dụng Expo là lựa chọn sáng suốt cho người mới bắt đầu vì nó giúp giảm thiểu các vấn đề cài đặt phức tạp. Ngày nay Expo đã phát triển rất mạnh và không còn nhiều giới hạn như trước đây, nên tôi cũng khuyên bạn sử dụng nó cho các dự án thực tế nhé!

    Khởi Tạo Dự Án Expo (06:21-17:45)

    Chúng ta bắt đầu bằng việc tạo một dự án Expo mới và cấu trúc thư mục để mọi thứ được tổ chức hợp lý. Bước này giúp đặt nền tảng cho toàn bộ ứng dụng.

    Điểm Chính:

    • Sử dụng lệnh npx create-expo-app@latest food-ordering -t typescript-navigation để tạo dự án
    • Tạo thư mục src và di chuyển các folder app, components, constants vào đó
    • Sử dụng npm start để chạy Expo development server
    • Di chuyển data mẫu từ asset bundle vào folder assets
    • Sử dụng Git để theo dõi các thay đổi trong dự án
    
        # Tạo project mới
        npx create-expo-app@latest food-ordering -t typescript-navigation
        
        # Di chuyển vào thư mục dự án
        cd food-ordering
        
        # Chạy server phát triển
        npm start
        

    Ý Kiến Của Mình:

    Việc tổ chức code trong thư mục src là một best practice mà mình luôn áp dụng. Nó giúp phân biệt rõ ràng code của bạn với các file cấu hình. Ngoài ra, việc commit thường xuyên cũng rất quan trọng – đừng đợi đến khi có vấn đề mới hối hận vì không có checkpoint để quay lại nhé!

    Làm Việc Với UI Component (17:46-24:30)

    Trong phần này, chúng ta học cách sử dụng các component có sẵn của React Native để xây dựng giao diện người dùng. Chúng ta sẽ tạo ra một item hiển thị thông tin của một sản phẩm pizza.

    Điểm Chính:

    • Sử dụng JSX syntax để render UI elements
    • Làm việc với các component cơ bản: View, Text, Image
    • Styling components với StyleSheet
    • Sử dụng biến JavaScript trong JSX với cú pháp {curlyBraces}
    • Styling với các thuộc tính như width, height, padding, borderRadius
    
        // Tạo component hiển thị thông tin pizza
        
          
          {product.name}
          ${product.price}
        
        
        // Styling với StyleSheet
        const styles = StyleSheet.create({
          container: {
            backgroundColor: 'white',
            padding: 10,
            borderRadius: 10,
            flex: 1,
          },
          image: {
            width: '100%',
            aspectRatio: 1,
          },
          title: {
            fontSize: 18,
            fontWeight: '600',
            marginVertical: 10,
          },
          price: {
            color: Colors.light.tint,
            fontWeight: 'bold',
          },
        });
        

    Ý Kiến Của Mình:

    Mình thích cách React Native sử dụng Flexbox cho layout, rất giống CSS trên web nhưng có một số điểm khác biệt nhỏ. Chẳng hạn, flexDirection mặc định là ‘column’ thay vì ‘row’. Việc hiểu rõ các thuộc tính styling này sẽ giúp bạn tạo layout nhanh và chính xác hơn nhiều đấy!

    Tạo Component Tùy Chỉnh (24:31-30:15)

    Trong phần này, chúng ta học cách tạo ra components tùy chỉnh và tái sử dụng chúng trong ứng dụng. Component hóa là một trong những khái niệm quan trọng nhất trong React và React Native.

    Điểm Chính:

    • Tạo component tùy chỉnh ProductListItem
    • Truyền dữ liệu từ component cha xuống component con thông qua props
    • Tổ chức file riêng cho mỗi component để dễ quản lý
    • Sử dụng export/import để chia sẻ components giữa các file
    
        // components/ProductListItem.tsx
        export default function ProductListItem({ product }) {
          return (
            
              
              {product.name}
              ${product.price}
            
          );
        }
        
        // Sử dụng trong component khác
        import ProductListItem from '@components/ProductListItem';
        
        // Trong component cha
        
        
        

    Ý Kiến Của Mình:

    Việc tách components ra thành các file riêng biệt không chỉ giúp code sạch đẹp hơn mà còn rất thuận lợi khi làm việc nhóm. Mỗi component nên có một trách nhiệm duy nhất (single responsibility principle) – đây là một best practice mà mình luôn tuân theo khi phát triển React/React Native. Nó giúp code dễ test, dễ maintain và dễ mở rộng hơn nhiều!

    TypeScript & Type Safety (30:16-37:10)

    Chúng ta tìm hiểu cách sử dụng TypeScript để tạo ra các component an toàn về kiểu dữ liệu. TypeScript giúp bắt lỗi ngay khi code thay vì khi chạy ứng dụng.

    Điểm Chính:

    • Định nghĩa interface/type cho props của component
    • Sử dụng TypeScript để bắt lỗi sớm và có trải nghiệm code tốt hơn
    • Xử lý các trường hợp null/undefined với fallback values
    • Cấu hình TypeScript path aliases để import dễ dàng hơn
    
        // Định nghĩa type cho props
        type ProductListItemProps = {
          product: Product;
        };
        
        // Sử dụng type trong component
        const ProductListItem = ({ product }: ProductListItemProps) => {
          return (
            
              
              {product.name}
              ${product.price}
            
          );
        };
        
        // Cấu hình path aliases trong tsconfig.json
        {
          "compilerOptions": {
            "baseUrl": ".",
            "paths": {
              "@components/*": ["src/components/*"],
              "@assets/*": ["assets/*"],
              "@/*": ["src/*"]
            }
          }
        }
        

    Ý Kiến Của Mình:

    TypeScript là một công cụ không thể thiếu trong các dự án lớn! Mình đã từng làm việc với cả JavaScript thuần và TypeScript, và có thể nói rằng TypeScript giúp giảm đáng kể bugs và tăng productivity. Path aliases cũng là một tip nhỏ nhưng rất hữu ích – thay vì phải nhớ và viết các đường dẫn tương đối phức tạp, bạn có thể import một cách ngắn gọn và rõ ràng hơn nhiều.

    Hiển Thị Danh Sách Với FlatList (37:11-43:30)

    Chúng ta học cách sử dụng FlatList – một component mạnh mẽ của React Native để hiển thị danh sách có thể cuộn được và tối ưu hiệu năng.

    Điểm Chính:

    • Sử dụng FlatList để render danh sách sản phẩm có thể scroll
    • Thiết lập numColumns để tạo grid layout
    • Styling các item trong grid với flex
    • Sử dụng gap hoặc margin để tạo khoảng cách giữa các item
    • Tùy chỉnh contentContainerStyle và columnWrapperStyle
    
         }
          numColumns={2}
          contentContainerStyle={{ padding: 10, gap: 10 }}
          columnWrapperStyle={{ gap: 10 }}
        />
        

    Ý Kiến Của Mình:

    FlatList là một trong những component quan trọng nhất khi làm việc với danh sách dữ liệu trong React Native. Khác với ScrollView, FlatList chỉ render các items đang hiển thị trên màn hình, giúp tăng performance đáng kể với danh sách lớn. Mẹo nhỏ: khi cần spacing giữa các items, dùng gap trong contentContainerStyle thường sạch sẽ hơn là styling từng item riêng lẻ đấy!

    Thiết Lập Navigation & Route (43:31-59:45)

    Trong phần này, chúng ta học cách thiết lập navigation giữa các màn hình sử dụng Expo Router – một thư viện navigation mạnh mẽ dựa trên file-system.

    Điểm Chính:

    • Hiểu cách Expo Router sử dụng cấu trúc thư mục để tạo routes
    • Tạo trang chi tiết sản phẩm và liên kết đến nó từ danh sách
    • Làm việc với dynamic routes và route parameters
    • Sử dụng useLocalSearchParams để lấy parameters từ URL
    • Thiết lập nested navigators (stack trong tabs)
    • Tùy chỉnh headers và navigation options
    
        // src/app/[id].tsx - Dynamic route cho chi tiết sản phẩm
        import { useLocalSearchParams } from 'expo-router';
        
        export default function ProductDetails() {
          // Lấy ID từ URL parameters
          const { id } = useLocalSearchParams();
          
          // Lấy thông tin sản phẩm dựa trên ID
          const product = products.find(p => p.id.toString() === id);
          
          return (
            
              Product Details for ID {id}
              {product && (
                <>
                  
                  {product.name}
                  ${product.price}
                </>
              )}
            
          );
        }
        
        // Sử dụng Link để điều hướng
        import { Link } from 'expo-router';
        
        // Trong ProductListItem
        
          
            {/* Component content */}
          
        
        

    Ý Kiến Của Mình:

    Expo Router là một cách tiếp cận navigation khá mới mẻ trong React Native và mình thấy nó rất trực quan! Việc navigation dựa vào file-system giúp bạn dễ dàng hình dung được cấu trúc của ứng dụng. Khái niệm nested navigation có thể hơi khó hiểu lúc đầu, nhưng một khi đã quen, bạn sẽ thấy nó rất linh hoạt để tạo ra các flow phức tạp trong ứng dụng.

    Màn Hình Chi Tiết Sản Phẩm (59:46-01:17:20)

    Chúng ta xây dựng màn hình chi tiết sản phẩm với các tính năng như chọn kích cỡ pizza và thêm vào giỏ hàng, đồng thời học cách quản lý state trong React Native.

    Điểm Chính:

    • Sử dụng useState hook để quản lý trạng thái được chọn
    • Tạo selector cho kích cỡ pizza (S, M, L, XL)
    • Styling có điều kiện (conditional styling) dựa trên state
    • Xử lý sự kiện khi người dùng tương tác
    • Tạo button component có thể tái sử dụng
    
        // State cho kích cỡ đã chọn
        const [selectedSize, setSelectedSize] = useState('M');
        
        // Render selector
        
          {sizes.map((size) => (
             setSelectedSize(size)}
            >
              
                {size}
              
            
          ))}
        
        
        // Button thêm vào giỏ hàng
        

    Ý Kiến Của Mình:

    Quản lý state là kỹ năng cực kỳ quan trọng trong React/React Native. Việc sử dụng useState hook đơn giản nhưng mạnh mẽ, giúp component của bạn phản ứng với tương tác của người dùng. Mình thích cách tutorial này thể hiện conditional styling – đây là pattern rất phổ biến khi làm việc với UI động. Hãy nhớ rằng trong React Native, style có thể là một array các objects, giúp việc kết hợp nhiều styles trở nên dễ dàng!

    Shopping Cart & React Context (01:17:21-01:43:30)

    Chúng ta học cách triển khai giỏ hàng bằng React Context và hiểu cách chia sẻ state giữa các components không liên quan trực tiếp.

    Điểm Chính:

    • Tạo và sử dụng React Context để quản lý giỏ hàng
    • Tạo custom hook (useCart) để dễ dàng truy cập cart context
    • Triển khai các chức năng: thêm sản phẩm, cập nhật số lượng, tính tổng tiền
    • Hiển thị giỏ hàng dưới dạng modal
    • Sử dụng StatusBar để điều chỉnh màu sắc thanh trạng thái
    
        // Tạo Context và Provider
        const CartContext = createContext({
          items: [],
          addItem: () => {},
          updateQuantity: () => {},
          total: 0,
        });
        
        export const CartProvider = ({ children }: { children: React.ReactNode }) => {
          const [items, setItems] = useState<CartItem[]>([]);
          
          // Thêm sản phẩm vào giỏ hàng
          const addItem = (product: Product, size: PizzaSize) => {
            // Kiểm tra sản phẩm đã tồn tại trong giỏ
            const existingItem = items.find(
              item => item.product.id === product.id && item.size === size
            );
            
            if (existingItem) {
              // Nếu đã tồn tại, tăng số lượng
              updateQuantity(existingItem.id, 1);
              return;
            }
            
            // Thêm sản phẩm mới
            setItems([
              {
                id: randomUUID(),
                product,
                size,
                quantity: 1,
              },
              ...items,
            ]);
          };
          
          // Cập nhật số lượng
          const updateQuantity = (itemId: string, amount: -1 | 1) => {
            setItems(
              items.map(item => {
                if (item.id !== itemId) return item;
                
                return {
                  ...item,
                  quantity: item.quantity + amount,
                };
              }).filter(item => item.quantity > 0)
            );
          };
          
          // Tính tổng tiền
          const total = items.reduce(
            (sum, item) => sum + item.product.price * item.quantity,
            0
          );
          
          return (
            
              {children}
            
          );
        };
        
        // Custom hook
        export const useCart = () => useContext(CartContext);
        

    Ý Kiến Của Mình:

    React Context là giải pháp tuyệt vời để share state giữa các components mà không cần “prop drilling”. Việc tạo custom hook làm cho code của bạn sạch sẽ hơn nhiều – thay vì phải lặp lại useContext ở mọi nơi, bạn có thể dùng hook ngắn gọn! Một tip nhỏ: luôn tạo các hàm xử lý state (như addItem, updateQuantity) bên trong Provider để tránh re-renders không cần thiết.

    Phần Admin (01:43:31-01:53:40)

    Chúng ta xây dựng giao diện quản trị (admin) cho ứng dụng, cho phép quản lý sản phẩm và đơn hàng.

    Điểm Chính:

    • Tạo cấu trúc thư mục phân biệt cho user và admin
    • Điều chỉnh bottom tab navigator cho admin với style khác biệt
    • Thiết lập navigation giữa user và admin interface
    • Xử lý path segments để đảm bảo các route hoạt động chính xác
    • Thêm các chức năng chỉ dành cho admin như create/edit/delete sản phẩm
    
        // Phân biệt admin và user bằng style
        // app/admin/_layout.tsx
        
           ,
            }}
          />
           ,
            }}
          />
        
        
        // Component để điều hướng
        // app/index.tsx
        export default function HomeScreen() {
          return (
            
              Food Ordering App
              
                
                  
                    User
                  
                
                
                  
                    Admin
                  
                
              
            
          );
        }
        

    Ý Kiến Của Mình:

    Việc tách biệt UI cho user và admin là một chiến lược thiết kế phổ biến trong các ứng dụng thực tế. Mình đánh giá cao cách tutorial này sử dụng styling và navigation để phân biệt hai phần. Tuy nhiên, trong thực tế, mình thường thêm một lớp bảo mật để đảm bảo rằng chỉ có admin mới có thể truy cập vào admin area – điều này sẽ được xử lý ở phần authentication sau.

    Tạo Sản Phẩm & Form (01:53:41-02:11:15)

    Chúng ta học cách xây dựng form để tạo và chỉnh sửa sản phẩm, xử lý input từ người dùng và thêm validation.

    Điểm Chính:

    • Làm việc với TextInput để nhập liệu
    • Quản lý form state với useState
    • Triển khai form validation
    • Xử lý image picker để chọn hình ảnh từ thư viện
    • Tạo component Button tái sử dụng
    • Xử lý cả tạo mới và cập nhật sản phẩm trong cùng một màn hình
    
        // State quản lý form
        const [name, setName] = useState('');
        const [price, setPrice] = useState('');
        const [image, setImage] = useState(null);
        const [errors, setErrors] = useState('');
        const [isLoading, setIsLoading] = useState(false);
        
        // Validation
        const validateInput = () => {
          setErrors('');
          if (!name) {
            setErrors('Name is required');
            return false;
          }
          if (!price) {
            setErrors('Price is required');
            return false;
          }
          if (isNaN(parseFloat(price))) {
            setErrors('Price is not a number');
            return false;
          }
          return true;
        };
        
        // Chọn hình ảnh
        const pickImage = async () => {
          let result = await ImagePicker.launchImageLibraryAsync({
            mediaTypes: ImagePicker.MediaTypeOptions.Images,
            allowsEditing: true,
            aspect: [4, 3],
            quality: 1,
          });
        
          if (!result.canceled) {
            setImage(result.assets[0].uri);
          }
        };
        
        // Xử lý form submit
        const onSubmit = () => {
          if (isUpdating) {
            onUpdate();
          } else {
            onCreate();
          }
        };
        

    Ý Kiến Của Mình:

    Form là một phần quan trọng trong hầu hết các ứng dụng, và cách xử lý form trong React Native có một số điểm khác biệt so với web. Việc tự xây dựng validation như trong tutorial là cách tiếp cận tốt để hiểu các nguyên lý cơ bản. Trong các dự án lớn hơn, mình thường sử dụng thư viện như Formik hoặc React Hook Form để quản lý form phức tạp, kết hợp với Yup hoặc Zod để validation – chúng giúp giảm thiểu boilerplate code rất nhiều!

    Tích Hợp Supabase (02:11:16-02:26:30)

    Trong phần này, chúng ta bắt đầu tích hợp Supabase làm backend cho ứng dụng, cài đặt và cấu hình Supabase trong dự án React Native.

    Điểm Chính:

    • Tạo dự án Supabase mới
    • Cài đặt Supabase JavaScript client
    • Cấu hình Supabase trong ứng dụng React Native
    • Hiểu về Row Level Security (RLS) trong Supabase
    • Thiết lập các policy cơ bản cho bảo mật database
    
        // src/lib/supabase.ts
        import 'react-native-url-polyfill/auto';
        import { createClient } from '@supabase/supabase-js';
        import { Database } from '../database.types';
        
        const supabaseUrl = process.env.EXPO_PUBLIC_SUPABASE_URL || '';
        const supabaseKey = process.env.EXPO_PUBLIC_SUPABASE_ANON_KEY || '';
        
        export const supabase = createClient(supabaseUrl, supabaseKey);
        

    Ý Kiến Của Mình:

    Supabase là một Backend-as-a-Service mạnh mẽ mà mình đã sử dụng trong nhiều dự án. Điểm mạnh của nó so với Firebase là bạn có quyền truy cập vào PostgreSQL thực sự, không bị giới hạn bởi một NoSQL data model. Row Level Security là một tính năng tuyệt vời của PostgreSQL mà Supabase tận dụng triệt để – nó cho phép bạn kiểm soát quyền truy cập ở cấp độ từng dòng dữ liệu một cách chi tiết.

    Authentication (02:26:31-02:45:45)

    Chúng ta triển khai hệ thống xác thực người dùng sử dụng Supabase Authentication, bao gồm đăng ký, đăng nhập và quản lý session.

    Điểm Chính:

    • Thiết lập Supabase Authentication
    • Tạo màn hình đăng ký và đăng nhập
    • Sử dụng AuthProvider để quản lý trạng thái đăng nhập
    • Bảo vệ routes dựa trên trạng thái xác thực
    • Phân quyền admin/user dựa trên nhóm người dùng
    
        // Đăng ký người dùng
        const signUpWithEmail = async () => {
          setIsLoading(true);
          const { error } = await supabase.auth.signUp({
            email,
            password,
          });
          
          if (error) {
            Alert.alert('Error', error.message);
          }
          setIsLoading(false);
        };
        
        // Đăng nhập người dùng
        const signInWithEmail = async () => {
          setIsLoading(true);
          const { error } = await supabase.auth.signInWithPassword({
            email,
            password,
          });
          
          if (error) {
            Alert.alert('Error', error.message);
          }
          setIsLoading(false);
        };
        
        // AuthProvider
        const AuthProvider = ({ children }) => {
          const [session, setSession] = useState(null);
          const [profile, setProfile] = useState(null);
          const [isLoading, setIsLoading] = useState(true);
          
          useEffect(() => {
            // Fetch current session
            const fetchSession = async () => {
              const { data, error } = await supabase.auth.getSession();
              if (data?.session) {
                setSession(data.session);
                fetchProfile(data.session);
              }
              setIsLoading(false);
            };
            
            fetchSession();
            
            // Subscribe to auth changes
            const { data } = supabase.auth.onAuthStateChange((_event, session) => {
              setSession(session);
              if (session) {
                fetchProfile(session);
              }
            });
            
            return () => {
              data?.subscription.unsubscribe();
            };
          }, []);
          
          return (
            
              {children}
            
          );
        };
        

    Ý Kiến Của Mình:

    Authentication là điểm mạnh của Supabase – việc setup một hệ thống xác thực an toàn chỉ mất vài phút. Cách sử dụng React Context để quản lý session trong tutorial là pattern rất phổ biến và hiệu quả. Mình đặc biệt thích cách họ triển khai protected routes bằng cách render component khác nhau dựa trên trạng thái đăng nhập. Trong các dự án thực tế, mình thường thêm refresh token logic để đảm bảo session của người dùng không bị hết hạn giữa chừng.

    Database & CRUD Operations (02:45:46-03:23:10)

    Chúng ta thiết kế database và triển khai đầy đủ các thao tác CRUD (Create, Read, Update, Delete) cho sản phẩm và đơn hàng sử dụng React Query và Supabase.

    Điểm Chính:

    • Thiết kế schema database: products, orders, order_items
    • Sử dụng React Query để quản lý state server
    • Tạo custom hooks cho các thao tác CRUD
    • Xử lý relationship giữa các bảng
    • Cài đặt TypeScript integration với Supabase
    • Caching và invalidation quạn lý cache
    
        // Custom hook đọc danh sách sản phẩm
        export const useProductList = () => {
          return useQuery({
            queryKey: ['products'],
            queryFn: async () => {
              const { data, error } = await supabase
                .from('products')
                .select('*');
              
              if (error) {
                throw new Error(error.message);
              }
              
              return data;
            },
          });
        };
        
        // Custom hook tạo sản phẩm mới
        export const useInsertProduct = () => {
          const queryClient = useQueryClient();
          
          return useMutation({
            mutationFn: async (data: InsertTables<'products'>) => {
              const { data: newProduct, error } = await supabase
                .from('products')
                .insert(data)
                .select('*')
                .single();
              
              if (error) {
                throw new Error(error.message);
              }
              
              return newProduct;
            },
            onSuccess: () => {
              queryClient.invalidateQueries({ queryKey: ['products'] });
            },
          });
        };
        
        // Sử dụng hooks trong component
        const ProductsList = () => {
          const { data: products, error, isLoading } = useProductList();
          
          if (isLoading) {
            return ;
          }
          
          if (error) {
            return Failed to fetch products;
          }
          
          return (
             }
              numColumns={2}
              contentContainerStyle={{ padding: 10, gap: 10 }}
              columnWrapperStyle={{ gap: 10 }}
            />
          );
        };
        

    Ý Kiến Của Mình:

    React Query là một thư viện tuyệt vời để xử lý server state trong React. Cách tutorial này tạo ra các custom hooks để wrap các logic CRUD là một pattern mà mình rất thích – nó tạo ra một API sạch sẽ, dễ sử dụng và dễ test. Việc sử dụng invalidateQueries để cập nhật cache là một trong những tính năng mạnh mẽ nhất của React Query, giúp UI luôn đồng bộ với dữ liệu trên server mà không cần code thêm.

    Tích Hợp Supabase Storage (03:23:11-03:32:40)

    Chúng ta học cách sử dụng Supabase Storage để lưu trữ và quản lý hình ảnh sản phẩm trên cloud.

    Điểm Chính:

    • Tạo storage bucket trong Supabase
    • Upload hình ảnh lên Supabase Storage
    • Tạo component RemoteImage để hiển thị ảnh từ Supabase
    • Xử lý lỗi và fallback image
    • Thiết lập security policies cho storage
    
        // Upload hình ảnh lên Supabase Storage
        const uploadImage = async (uri: string) => {
          try {
            const fileExtension = uri.split('.').pop();
            const fileName = `${randomUUID()}.${fileExtension}`;
            
            const fileB64 = await FileSystem.readAsStringAsync(uri, {
              encoding: FileSystem.EncodingType.Base64,
            });
            
            const { data, error } = await supabase.storage
              .from('product-images')
              .upload(fileName, decode(fileB64), {
                contentType: 'image/png',
              });
              
            if (error) {
              throw new Error(error.message);
            }
            
            return data.path;
          } catch (e) {
            console.log(e);
            return null;
          }
        };
        
        // Component hiển thị ảnh từ Supabase Storage
        const RemoteImage = ({ path, fallback }: { path: string | null; fallback: string }) => {
          const [image, setImage] = useState(null);
          
          useEffect(() => {
            if (!path) return;
            
            const downloadImage = async () => {
              try {
                const { data, error } = await supabase.storage
                  .from('product-images')
                  .download(path);
                  
                if (error) {
                  throw error;
                }
                
                const fr = new FileReader();
                fr.readAsDataURL(data);
                fr.onload = () => {
                  setImage(fr.result as string);
                };
              } catch (e) {
                console.log(e);
              }
            };
            
            downloadImage();
          }, [path]);
          
          if (!image) {
            return ;
          }
          
          return ;
        };
        

    Ý Kiến Của Mình:

    Supabase Storage là một giải pháp lưu trữ file khá tốt cho các ứng dụng nhỏ và vừa. Mình đánh giá cao cách tutorial này tạo một component RemoteImage để handle việc download và hiển thị ảnh – đây là một pattern rất tốt để tái sử dụng code. Trong các dự án lớn hơn, mình thường kết hợp với CDN như Cloudflare để tối ưu performance khi hiển thị ảnh. Và đừng quên xử lý các edge case như khi ảnh không tải được!

    Real-time Updates (03:32:41-03:43:20)

    Chúng ta học cách triển khai real-time updates sử dụng Supabase Realtime để cập nhật UI ngay lập tức khi có thay đổi dữ liệu.

    Điểm Chính:

    • Bật Realtime cho các bảng trong Supabase
    • Subscribe các sự kiện insert, update, delete
    • Cập nhật UI khi nhận được real-time events
    • Kết hợp React Query và Realtime để tối ưu UX
    • Unsubscribe khi component unmount
    
        // Custom hook để lắng nghe khi có đơn hàng mới
        export const useInsertOrderSubscription = () => {
          const queryClient = useQueryClient();
          
          useEffect(() => {
            const subscription = supabase
              .channel('schema-db-changes')
              .on(
                'postgres_changes',
                {
                  event: 'INSERT',
                  schema: 'public',
                  table: 'orders',
                },
                (payload) => {
                  // Invalidate and refetch orders query
                  queryClient.invalidateQueries({ queryKey: ['orders'] });
                }
              )
              .subscribe();
              
            return () => {
              subscription.unsubscribe();
            };
          }, []);
        };
        
        // Custom hook để lắng nghe khi đơn hàng cập nhật trạng thái
        export const useUpdateOrderSubscription = (id: number) => {
          const queryClient = useQueryClient();
          
          useEffect(() => {
            const subscription = supabase
              .channel(`order-${id}-changes`)
              .on(
                'postgres_changes',
                {
                  event: 'UPDATE',
                  schema: 'public',
                  table: 'orders',
                  filter: `id=eq.${id}`,
                },
                (payload) => {
                  // Invalidate and refetch specific order query
                  queryClient.invalidateQueries({ 
                    queryKey: ['orders', id] 
                  });
                }
              )
              .subscribe();
              
            return () => {
              subscription.unsubscribe();
            };
          }, [id]);
        };
        

    Ý Kiến Của Mình:

    Real-time features là điều giúp ứng dụng của bạn nổi bật! Mình rất thích cách Supabase cung cấp real-time subscriptions dựa trên Postgres, khiến cho việc triển khai các tính năng như thông báo đơn hàng mới hay cập nhật trạng thái trở nên đơn giản. Kết hợp với React Query’s invalidateQueries là một pattern rất elegant – thay vì phải tự cập nhật state, bạn chỉ cần báo React Query fetch lại data, giúp code của bạn sạch sẽ và dễ maintain hơn nhiều.

    Tích Hợp Thanh Toán Với Stripe (03:43:21-04:07:30)

    Chúng ta tích hợp Stripe để xử lý thanh toán trong ứng dụng, bao gồm cả server-side và client-side logic.

    Điểm Chính:

    • Thiết lập Stripe account và API keys
    • Tạo Supabase Edge Function để tạo payment intent
    • Tích hợp Stripe React Native trong ứng dụng
    • Triển khai payment flow hoàn chỉnh
    • Lưu Stripe customer ID cho thanh toán lặp lại
    
        // Supabase Edge Function (payment-sheet/index.ts)
        import { serve } from 'https://deno.land/std@0.168.0/http/server.ts';
        import { stripe } from '../_utils/stripe.ts';
        
        serve(async (req) => {
          try {
            const { amount } = await req.json();
            
            // Tạo payment intent
            const paymentIntent = await stripe.paymentIntents.create({
              amount: amount,
              currency: 'usd',
            });
            
            // Trả về client secret cho React Native app
            const response = {
              paymentIntent: paymentIntent.client_secret,
              publishableKey: Deno.env.get('EXPO_PUBLIC_STRIPE_PUBLISHABLE_KEY'),
            };
            
            return new Response(JSON.stringify(response), {
              headers: { 'Content-Type': 'application/json' },
            });
          } catch (error) {
            return new Response(JSON.stringify({ error: error.message }), {
              headers: { 'Content-Type': 'application/json' },
              status: 400,
            });
          }
        });
        
        // Client-side (React Native)
        import { initPaymentSheet, presentPaymentSheet } from '@stripe/stripe-react-native';
        
        const initializePaymentSheet = async (amount: number) => {
          try {
            // Gọi Edge Function để lấy payment intent
            const { data, error } = await supabase.functions.invoke('payment-sheet', {
              body: { amount: Math.floor(amount * 100) },
            });
            
            if (error || !data.paymentIntent || !data.publishableKey) {
              console.error('Error fetching payment params:', error);
              return;
            }
            
            // Khởi tạo payment sheet
            const { error: initError } = await initPaymentSheet({
              merchantDisplayName: 'NotJust.Dev Food',
              paymentIntentClientSecret: data.paymentIntent,
            });
            
            if (initError) {
              console.error('Error initializing payment sheet:', initError);
            }
          } catch (e) {
            console.error('Payment sheet error:', e);
          }
        };
        
        const openPaymentSheet = async () => {
          try {
            const { error } = await presentPaymentSheet();
            
            if (error) {
              Alert.alert('Payment failed', error.message);
              return false;
            }
            
            Alert.alert('Payment successful', 'Your order has been placed!');
            return true;
          } catch (e) {
            console.error('Payment presentation error:', e);
            return false;
          }
        };
        

    Ý Kiến Của Mình:

    Stripe là giải pháp thanh toán hàng đầu cho các ứng dụng hiện đại. Mình đánh giá cao cách tutorial tách biệt client logic và server logic bằng cách sử dụng Supabase Edge Functions – điều này tuân thủ best practices của Stripe về bảo mật. Đặc biệt quan trọng là việc không bao giờ đưa Stripe secret key vào client side code. Một tip hữu ích: khi triển khai payment trong production, hãy nhớ xác thực các webhook events từ Stripe để cập nhật trạng thái thanh toán trong database của bạn một cách đáng tin cậy.

    Push Notifications (04:07:31-04:29:30)

    Cuối cùng, chúng ta triển khai push notifications để thông báo cho admin khi có đơn hàng mới và thông báo cho người dùng khi trạng thái đơn hàng của họ thay đổi.

    Điểm Chính:

    • Cấu hình Expo Notifications
    • Xin quyền push notifications từ người dùng
    • Lưu trữ Expo push tokens trong database
    • Gửi push notifications giữa các thiết bị
    • Xử lý notifications khi ứng dụng ở foreground/background
    • Cài đặt EAS để chuẩn bị cho notifications trong production
    
        // Đăng ký để nhận push notifications
        const registerForPushNotifications = async () => {
          // Kiểm tra nếu đang chạy trên thiết bị thật
          const { isDevice } = Device.getDeviceTypeAsync();
          if (!isDevice) {
            alert('Phải sử dụng thiết bị thật để nhận push notifications');
            return;
          }
        
          // Kiểm tra quyền hiện tại
          const { status: existingStatus } = await Notifications.getPermissionsAsync();
          let finalStatus = existingStatus;
        
          // Nếu chưa có quyền, yêu cầu quyền
          if (existingStatus !== 'granted') {
            const { status } = await Notifications.requestPermissionsAsync();
            finalStatus = status;
          }
        
          if (finalStatus !== 'granted') {
            alert('Không thể nhận push notifications nếu không cấp quyền!');
            return;
          }
        
          // Lấy Expo push token
          const token = await Notifications.getExpoPushTokenAsync({
            projectId: Constants.expoConfig?.extra?.eas?.projectId,
          });
        
          return token.data;
        };
        
        // Lưu push token vào database
        const savePushToken = async (token: string) => {
          await supabase
            .from('profiles')
            .update({ expo_push_token: token })
            .eq('id', profile.id);
        };
        
        // Gửi push notification
        const sendPushNotification = async (
          token: string,
          title: string,
          body: string
        ) => {
          await fetch('https://exp.host/--/api/v2/push/send', {
            method: 'POST',
            headers: {
              'Content-Type': 'application/json',
            },
            body: JSON.stringify({
              to: token,
              title,
              body,
              sound: 'default',
            }),
          });
        };
        
        // Thông báo cho người dùng khi trạng thái đơn hàng thay đổi
        const notifyUserAboutOrderUpdate = async (order: Tables<'orders'>) => {
          // Lấy token của người dùng từ profile
          const { data } = await supabase
            .from('profiles')
            .select('expo_push_token')
            .eq('id', order.user_id)
            .single();
        
          if (!data?.expo_push_token) return;
        
          // Gửi thông báo
          await sendPushNotification(
            data.expo_push_token,
            `Đơn hàng của bạn đang ${order.status}`,
            `Đơn hàng #${order.id} đã được cập nhật!`
          );
        };
        

    Ý Kiến Của Mình:

    Push notifications là một tính năng quan trọng để giữ người dùng gắn bó với ứng dụng của bạn. Cách Expo đã đơn giản hóa việc triển khai push notifications là đáng kinh ngạc – thay vì phải thiết lập FCM (Firebase Cloud Messaging) và APN (Apple Push Notifications) riêng biệt, bạn chỉ cần sử dụng Expo Push API. Tuy nhiên, khi triển khai production, bạn sẽ cần thiết lập các credentials cho cả hai nền tảng. Mình khuyên bạn nên cân nhắc kỹ lưỡng tần suất gửi notifications – quá nhiều sẽ làm phiền người dùng, nhưng quá ít sẽ không giữ được engagement.

    Bài viết này tóm tắt video hướng dẫn tuyệt vời được tạo bởi NJ Developers. Nếu bạn thấy bản tóm tắt này hữu ích, hãy ủng hộ tác giả bằng cách xem toàn bộ video và đăng ký kênh của họ nhé.

    Share. Facebook Twitter Pinterest LinkedIn Tumblr Email
    Previous ArticleHệ Thống One-Station Versioning: Tạo Script Video Trong 1 Phút
    Next Article Đánh Giá Rukall: Trải Nghiệm Thực Tế Trong VIP Coding Olympics
    minhminh
    • Website

    Related Posts

    Javascript

    Cài Đặt n8n Trên VPS Ubuntu – Hướng Dẫn Chi Tiết

    Tháng 5 28, 2025
    Javascript

    Xây Dựng Website Camp Hè Với Next.js 15 và Strapi 5 [Hướng Dẫn Chi Tiết]

    Tháng 5 25, 2025
    Javascript

    Tóm Tắt Khóa Học JavaScript Cho Người Mới Bắt Đầu – Học Nhanh!

    Tháng 5 1, 2025
    Add A Comment
    Leave A Reply Cancel Reply

    Demo
    Top Posts

    Khắc Phục Tận Gốc Thói Quen Trì Hoãn Với “The End of Procrastination”: Hướng Dẫn Chi Tiết Trong 90 Ngày

    Tháng 5 5, 202510 Views

    Programmable Agentic Coding: Tận Dụng Claude Code Để Tối Ưu Công Việc Lập Trình

    Tháng 5 3, 20256 Views

    Tìm Hiểu Node Trong N8N: Khối Xây Dựng Cơ Bản Cho Workflow

    Tháng 5 6, 20254 Views
    Stay In Touch
    • Facebook
    • YouTube
    • TikTok
    • WhatsApp
    • Twitter
    • Instagram
    Latest Reviews

    Subscribe to Updates

    Get the latest tech news from FooBar about tech, design and biz.

    Demo
    Most Popular

    Khắc Phục Tận Gốc Thói Quen Trì Hoãn Với “The End of Procrastination”: Hướng Dẫn Chi Tiết Trong 90 Ngày

    Tháng 5 5, 202510 Views

    Programmable Agentic Coding: Tận Dụng Claude Code Để Tối Ưu Công Việc Lập Trình

    Tháng 5 3, 20256 Views

    Tìm Hiểu Node Trong N8N: Khối Xây Dựng Cơ Bản Cho Workflow

    Tháng 5 6, 20254 Views
    Our Picks

    Task Management cho Cursor AI – Tăng 10x Hiệu Quả Coding

    Tháng 5 28, 2025

    Cài Đặt n8n Trên VPS Ubuntu – Hướng Dẫn Chi Tiết

    Tháng 5 28, 2025

    Context.7: Tool RAG Miễn Phí Cho AI Coding Assistant

    Tháng 5 28, 2025

    Subscribe to Updates

    Get the latest creative news from FooBar about art, design and business.

    Facebook X (Twitter) Instagram Pinterest
    • Home
    • Get In Touch
    © 2025 ThemeSphere. Designed by ThemeSphere.

    Type above and press Enter to search. Press Esc to cancel.