Tạo 3 select input để chọn địa chỉ giao hàng cùng React (P1)

Chắc mọi người cũng không quá xa lạ gì bộ ba select địa chỉ hành chính Tỉnh/Thành – Quận/Huyện – Phường/Xã được tổ chức dưới dạng là các select input trong nhiều website, đặc biệt là các trang thương mại điện tử. Bài viết sẽ tập trung về hành trình của mình trong việc dựng lại các input trên cùng React.

Source code có thể tham khảo tại Github và demo tại Codesandbox.

Giao diện demo của mình cho bài viết

Phân tích bài toán

Về giao diện của bài toán chắc đã quá rõ ràng đúng không? Chỉ cần 3 select input và một chút CSS để sắp xếp bố cục cho dễ nhìn.

Nhưng sự lắt léo ở đây sẽ nằm ở việc xử lí event vì sự phụ thuộc lẫn nhau giữa các cấp đơn vị hành chính, bạn có thể thử trên vài website đặt hàng có phần chọn địa chỉ hành chính để tìm hiểu logic, sau đây là phần phân tích của mình:

  • Thông tin đơn vị hành chính cấp thấp phải lấy từ thông tin của cấp cao hơn nó một bậc, theo thứ tự Tỉnh/Thành -> Quận/Huyện -> Phường/Xã.
  • Khi thay đổi lựa chọn đơn vị hành chính cấp cao, cần xóa lựa chọn của cấp thấp hơn nó, sau đó tải thông tin của đơn vị thấp hơn nó 1 bậc.
  • Không được phép chọn đơn vị hành chính cấp thấp khi trước đó chưa có thông tin của đơn vị hành chính cấp cao hơn nó một bậc.

Cụ thể hơn về mô tả UX sẽ như sau:

(Gọi 3 select input Tỉnh/Thành, Quận/Huyện, Phường/Xã là CitySelect, DistrictSelect, WardSelect).

EventFetch DataSelect InputsGhi chú
Tải thông
tin mới
Tỉnh/ThànhCitySelect nhận data và enable.CitySelect sẽ nhận data cố định.
Disable DistrictSelect và WardSelect.
Tải thông tin
từ địa chỉ
Tỉnh/Thành,
Quận/Huyện,
Phường/Xã
Select Inputs nhận data tương ứng.Enable toàn bộ select sau khi fetch đủ data.
Default option của select dựa trên thông tin địa chỉ.
Chọn Tỉnh/ThànhQuận/HuyệnDistrictSelect nhận data và enable.Disable DistrictSelect và WardSelect, xóa option đang chọn trước khi Fetch Data.
Chọn Quận/HuyệnPhường/XãWardSelect nhận data và enable.Disable WardSelect, xóa option đang chọn trước khi Fetch Data.
Chọn Phường/Xã
Các event cần xử lí cho bài toán

Bộ dữ liệu địa chỉ

Dĩ nhiên trước khi lao vào code thì phải cần nguyên liệu là dữ liệu địa chỉ rồi, dưới đây mình có một số gợi ý để các bạn có thể tự chuẩn bị cho mình bộ data.

Cách 1 – Hỏi xin và tự setup

Bạn có thể dễ dàng google hoặc hỏi xin trên các group, forum lập trình, sau đó là chuẩn bị setup tương ứng với bộ data source được chọn, ví dụ như tạo API ở backend (với SQL Script) hoặc JSON Server (với JSON file).

Nếu bộ data tìm được quá nhiều thông tin, bạn có thể drop bớt các column dư thừa (với SQL Script) hoặc tạo JSON mới chỉ filter những field cần thiết (với JSON file). Như trong demo của mình thì chỉ cần quan tâm về mã và tên địa chỉ (id và name) cho việc hiển thị và mapping theo mã là đủ.

Data rất chi tiết nhưng khá “cồng kềnh” để áp dụng cho demo
Với demo của mình, data chỉ cần đơn giản như thế này

Cách 2 – Gọi API từ nguồn khác

Vẫn là lân la google để tìm các nguồn cung cấp các public API và cách sử dụng. Nếu thích phiêu lưu khám phá, bạn có thể mày mò trong các trang như Shopee hay Tiki mục tạo địa chỉ giao hàng, tìm ra quy luật và cách call public API đơn vị hành chính và “mượn xài tạm” nhé.

Trang tạo địa chỉ giao hàng của Shopee, F12 lên và mò mẫm thôi

Cách 3 – Kết hợp cả 1 và 2

Trong project demo, mình chuẩn bị các file JSON về địa chỉ hành chính trong Github và call GET thông qua Raw Github User Content (đây cũng là một trick khá tiện để tận dụng Public Github Repository như một free JSON server, bằng việc nhấn vào button Raw tại file JSON đang view trên Github, bạn sẽ đến url raw.githubusercontent.com như một public GET Restful API).

Dĩ nhiên dữ liệu trước đó mình đã thực hiện “xin ké” từ Tiki về local thông qua việc phân tích và crawl các public API.

Bạn có thể tham khảo bộ data cũng như call API bằng của mình tại đây.

Về React Select

Trong demo mình sẽ sử dụng React Select, đây là thư viện khá phổ biến trong cộng đồng React khi cần tạo một select input với bộ built in API khá chi tiết và thuận tiện hơn nhiều so với sử dụng <select> và <option> thông thường.

Dưới đây là các thuộc tính mình đã sử dụng trong demo:

Thuộc tínhMô tảỨng dụng trong demo
isDisabledDisable select input khi thõa điều kiện.Select sẽ nhận isDisabled khi không có options nào (vừa load trang, vừa bị xóa option do event chọn mới đơn vị hành chính).
optionsHiển thị danh sách các option, nhận vào mảng là object gồm 2 thuộc tính chính là value và label (tương ứng với value và innerText trong <select>).Data fetching được dưới dạng là mảng các object gồm id và name => thực hiện mapping sang value và label và đổ vào select tương ứng.
onChangeTương tự onChange trong <select>, tuy nhiên input param sẽ là một option theo bao gồm {value, label}, thuận tiện cho việc xử lí cả thông tin value và text của option mới.Xử lí event khi chọn Tỉnh/Thành, Quận/Huyện, Phường/Xã được mô tả ở trên.
placeholderĐoạn text hiển thị khi chưa có option nào được chọn trong select, nhằm hỗ trợ người dùng biết select input được sử dụng cho việc gì.Tương tự với mô tả, gồm các placeholder Tỉnh/Thành, Quận/Huyện, Phường/Xã.
defaultValueDefault option của react-select, truyền vào một option bao gồm {value, label}, nếu là null sẽ xem như là chưa chọn option và hiện thị placeholder.Hiện thị thông tin địa chỉ trong trường hợp load từ địa chỉ cho trước.
Xử lí xóa thông tin option đơn vị hành chính cấp thấp nếu chọn option cấp cao hơn.
Các thuộc tính của react-select mình sử dụng trong demo

Trong quá trình thực hiện, khi reset option về trang thái chưa chọn, mình nhận ra react-select không thực hiện re-render lại. Sau quá trình lân la google, đã có một workaround khá ổn bằng việc sử dụng thuộc tính key dành cho các React Component để trigger việc rerender, bạn có thể tham khảo về key tại đâysolution mình tìm được.

Phần code cho UI (JSX)

Vì giao diện khá đơn giản và dễ hiểu nên phần code UI mình sẽ không phân tích sâu, mình vẫn sử dụng TailwindCSS, bạn có thể tùy biến UI theo ý thích, nhưng nhớ sử dụng 3 react-select để tương thích với code của mình.

Phần chúng ta cần tập trung sẽ là logic, handle event và state management, trong phần này mình sẽ chỉ giới thiệu qua các “nguyên liệu” cần chuẩn bị theo demo: các event và thuộc tính trong state cần quản lí.

(Kiểu dữ liệu option ở đây sẽ mặc định là object gồm hai thuộc tính value và label)

EventInputMô tả
onCitySelectoptionXử lí khi chọn một option Tỉnh/Thành
onDistrictSelectoptionXử lí khi chọn một option Quận/Huyện
onWardSelectoptionXử lí khi chọn một option Phường/Xã
onSubmitXử lí khi submit form, tuy nhiên ở bài viết này
mình sẽ không cover việc xử lí submit.
Các event cần xử lí trong bài toán

 

StateTypeMô tả
cityOptionsoption[]Danh sách Tỉnh/Thành cho CitySelect.
Mảng rỗng khi first load.
Nhận data là mảng option sau khi fetch data Tỉnh/Thành.
districtOptionsoption[]Danh sách Quận/Huyện cho DistrictSelect.
Mảng rỗng khi first load hoặc bị reset trong onCitySelect event.
Nhận data là mảng option sau khi fetch data Quận/Huyện tương ứng với Tỉnh/Thành.
wardOptionsoption[]Danh sách Phường/Xã cho WardSelect.
Mảng rỗng khi first load hoặc bị reset trong onCitySelect hoặc onDistrictSelect event.
Nhận data là mảng option sau khi fetch data Phường/Xã tương ứng với Quận/Huyện.
selectedCityoptionOption Tỉnh/Thành được chọn
Null khi first load.
selectedDistrictoptionOption Quận/Huyện được chọn.
Null khi first load hoặc bị reset trong onCitySelect event.
selectedWardoptionOption Phường/Xã được chọn.
Null khi first load hoặc bị reset trong onCitySelect hoặc onDistrictSelect event.
Một số state cần quản lý

Ngoài ra trong code của mình còn thêm một số state khác, tuy nhiên mình sẽ cover và phân tích kĩ về custom hook để xử lí các event trong bài viết sau, bạn có thể tham khảo bài viết này để có cái nhìn tổng quan hơn về custom hook.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s