State Retention (การรักษาสถานะ)
State Retention (การรักษาสถานะ) กรณีค้นหาข้อมูล
ต่อมาเรื่องค้นหา เมื่อคลิก details หลังจากคลิกกลับมา อยากให้ค่าที่ค้นหา ยังอยู่ เหมือนเดิม สามารถทำอย่างไรได้บ้าง ต้องเก็บเป็น session ไหม
นี่เป็นคำถามระดับ "Senior Developer" เลยครับ! ปัญหานี้เรียกว่า "State Retention (การรักษาสถานะ)" ซึ่งเป็นสิ่งที่แบ่งแยกระบบที่ใช้งานยาก (กดกลับแล้วต้องกรอกใหม่หมด) กับระบบที่ใช้งานง่าย (UX ดีเยี่ยม) หลายคนมักจะกระโดดไปใช้ Session ทันทีเมื่อเจอโจทย์นี้ แต่ในฐานะ Architect ผมต้องขอเบรกไว้ก่อนครับ เรามาวิเคราะห์กันว่าทำไม
Layer 1: Big Picture (ภาพรวมก่อน)
เรากำลังแก้ปัญหาอะไร? ผู้ใช้กรอกเงื่อนไขค้นหา (เช่น ปี 2024, สถานะ: รอชำระ) แล้วกดดู Detail ของรายการที่ 5 (หน้าเว็บเปลี่ยนไปหน้า Detail) พอผู้ใช้กดปุ่ม "กลับไปหน้าค้นหา" ค่าที่เคยกรอกไว้หายไปหมด และกลับไปหน้า 1 เสมอ
ควรใช้ Session ดีไหม? คำตอบคือ: ไม่แนะนำอย่างยิ่งครับ! (Anti-Pattern) ถ้าคุณเก็บ Search Parameters ไว้ใน Session จะเกิดบั๊กที่เรียกว่า "The Multi-Tab Problem" ลองนึกภาพ:
นี่เป็นคำถามระดับ "Senior Developer" เลยครับ! ปัญหานี้เรียกว่า "State Retention (การรักษาสถานะ)" ซึ่งเป็นสิ่งที่แบ่งแยกระบบที่ใช้งานยาก (กดกลับแล้วต้องกรอกใหม่หมด) กับระบบที่ใช้งานง่าย (UX ดีเยี่ยม) หลายคนมักจะกระโดดไปใช้ Session ทันทีเมื่อเจอโจทย์นี้ แต่ในฐานะ Architect ผมต้องขอเบรกไว้ก่อนครับ เรามาวิเคราะห์กันว่าทำไม
Layer 1: Big Picture (ภาพรวมก่อน)
เรากำลังแก้ปัญหาอะไร? ผู้ใช้กรอกเงื่อนไขค้นหา (เช่น ปี 2024, สถานะ: รอชำระ) แล้วกดดู Detail ของรายการที่ 5 (หน้าเว็บเปลี่ยนไปหน้า Detail) พอผู้ใช้กดปุ่ม "กลับไปหน้าค้นหา" ค่าที่เคยกรอกไว้หายไปหมด และกลับไปหน้า 1 เสมอ
ควรใช้ Session ดีไหม? คำตอบคือ: ไม่แนะนำอย่างยิ่งครับ! (Anti-Pattern) ถ้าคุณเก็บ Search Parameters ไว้ใน Session จะเกิดบั๊กที่เรียกว่า "The Multi-Tab Problem" ลองนึกภาพ:
- ผู้ใช้เปิด "Tab A" ค้นหา "ปี 2023" (Session จำว่า = 2023)
- ผู้ใช้เปิด "Tab B" ค้นหา "ปี 2024" (Session ถูกเขียนทับเป็น = 2024)
- ผู้ใช้กลับมาที่ "Tab A" แล้วกดเปลี่ยนหน้า (Page 2) ระบบจะดึงข้อมูลของ "ปี 2024" มาแสดงใน Tab A ทันที! ซึ่งจะทำให้ผู้ใช้สับสนและระบบดูเหมือนรวน
สถาปัตยกรรมที่ถูกต้อง (Stateless Approach): เราจะใช้เทคนิค "Pass Forward & Return" คือการแนบ URL ปัจจุบัน (ที่รวมเงื่อนไขค้นหาทั้งหมดและหน้า Page ปัจจุบันไว้แล้ว) ส่งตามไปด้วยตอนคลิกดู Detail และตอนกดปุ่มกลับ ก็แค่ Link กลับมาที่ URL เดิมนั้นครับ
Layer 2: Step-by-Step Implementation
เราจะปรับแก้แค่หน้า Blade เล็กน้อย โดยไม่ต้องไปยุ่งกับ Controller หรือสร้าง Session ให้รก Server เลยครับ
Layer 2: Step-by-Step Implementation
เราจะปรับแก้แค่หน้า Blade เล็กน้อย โดยไม่ต้องไปยุ่งกับ Controller หรือสร้าง Session ให้รก Server เลยครับ
Step 1: ส่ง URL ปัจจุบันพ่วงไปกับปุ่ม "ดูรายละเอียด" (ที่หน้า Index)
ในหน้าตารางค้นหา (sales.report-pt) ตรงปุ่มรูปดวงตา ให้เราเพิ่ม parameter พิเศษชื่อว่า return_url เข้าไปครับ ฟังก์ชัน request()->fullUrl() จะดึง URL ปัจจุบันทั้งหมด (รวมถึง ?search_status=1&page=2 ฯลฯ) ส่งตามไปด้วย
ในหน้าตารางค้นหา (sales.report-pt) ตรงปุ่มรูปดวงตา ให้เราเพิ่ม parameter พิเศษชื่อว่า return_url เข้าไปครับ ฟังก์ชัน request()->fullUrl() จะดึง URL ปัจจุบันทั้งหมด (รวมถึง ?search_status=1&page=2 ฯลฯ) ส่งตามไปด้วย
<td>
<a class="btn btn-dark"
{{-- แนบ URL ปัจจุบันที่รวมเงื่อนไขค้นหาไปในชื่อ return_url --}}
href="{{ route('sales.report-pt.detail', [
'id' => $order->id,
'return_url' => request()->fullUrl()
]) }}">
<i class="ri-eye-line me-1"></i>
</a>
</td>Step 2: สร้างปุ่ม "กลับ" ที่หน้า Detail
เมื่อผู้ใช้เข้ามาที่หน้า Detail ของคำสั่งซื้อ ให้เราสร้างปุ่ม "กลับไปหน้าค้นหา" โดยให้ปุ่มนี้อ่านค่า return_url ที่ส่งมา ถ้ามีก็ให้กลับไปที่นั่น แต่ถ้าไม่มี (เช่น ผู้ใช้ copy link หน้า detail ส่งให้เพื่อน) ก็ให้กลับไปหน้าค้นหาปกติ (Fallback)
เมื่อผู้ใช้เข้ามาที่หน้า Detail ของคำสั่งซื้อ ให้เราสร้างปุ่ม "กลับไปหน้าค้นหา" โดยให้ปุ่มนี้อ่านค่า return_url ที่ส่งมา ถ้ามีก็ให้กลับไปที่นั่น แต่ถ้าไม่มี (เช่น ผู้ใช้ copy link หน้า detail ส่งให้เพื่อน) ก็ให้กลับไปหน้าค้นหาปกติ (Fallback)
<div class="card-header d-flex justify-content-between align-items-center">
<h5 class="mb-0">รายละเอียดคำสั่งซื้อ #{{ $order->order_number }}</h5>
{{-- ปุ่มกลับ: อ่านค่า return_url จาก URL ถ้าไม่มีให้ใช้ route เริ่มต้น --}}
<a href="{{ request('return_url', route('sales.report-pt')) }}" class="btn btn-outline-secondary">
<i class="ri-arrow-left-line me-1"></i> กลับหน้าค้นหา
</a>
</div>Layer 3: Expert Insight (มุมมองระดับ Architect)
ทำไมวิธีนี้ถึงเป็น Best Practice ในระดับ Production?
ทำไมวิธีนี้ถึงเป็น Best Practice ในระดับ Production?
- Stateless Design: ระบบที่ทำงานได้เสถียรที่สุดคือระบบที่ไม่จำสถานะของผู้ใช้ (Stateless) การโยน State ไปไว้บน URL (Query String) ทำให้เราสามารถโยนโหลดของ Server ไปไว้ที่ Browser ของผู้ใช้แทน
- Sharable URLs (การแชร์ลิงก์): ข้อดีที่สุดของการให้ทุกอย่างอยู่บน URL คือ หากเจ้าหน้าที่ฝ่ายขาย ค้นหาข้อมูล "สถานะ: รอชำระ, ของปี 2024, หน้าที่ 3" แล้วเขาก๊อปปี้ URL ส่งผ่าน LINE ให้ผู้จัดการดู ผู้จัดการเปิดมาจะได้หน้าจอเหมือนกันเป๊ะ 100% (ถ้าใช้ Session ทำแบบนี้ไม่ได้เลยครับ ผู้จัดการเปิดมาจะเป็นหน้าว่างๆ)
- The Browser 'Back' Button: จริงๆ แล้ว ถ้าผู้ใช้กดปุ่มลูกศร "Back (<)" ของ Browser ค่ามันจะอยู่เหมือนเดิมตามธรรมชาติอยู่แล้ว แต่ในฐานะคนทำ UX เราต้องเผื่อใจไว้ว่าผู้ใช้มักจะหา "ปุ่มถอยหลัง" บนหน้าเว็บของเรามากกว่า เราจึงทำปุ่มนี้ขึ้นมาหลอกๆ ให้ทำงานเหมือนการกดปุ่ม Back ของ Browser นั่นเองครับ
laravel