วันจันทร์ที่ 31 มีนาคม พ.ศ. 2568

การจัดการฟอร์มใน Next.js ด้วย React Hook Form: ใช้ง่าย ประหยัดแรง

ถ้าพูดถึงการจัดการฟอร์มใน React หรือ Next.js หลายคนคงเคยเจอปัญหาปวดหัวกับการจัดการ state, validation, และการ submit ข้อมูลกันใช่ไหม? วันนี้เราจะมาทำความรู้จักกับ React Hook Form ซึ่งเป็นหนึ่งในไลบรารีจัดการฟอร์มที่ดีที่สุดสำหรับ React และ Next.js ทำให้ชีวิตนักพัฒนาง่ายขึ้นเยอะ!

ทำไมต้องใช้ React Hook Form?

โดยปกติ ถ้าเราจัดการฟอร์มใน React ด้วย useState() เราจะต้องสร้าง state แยกกันตามฟิลด์ของฟอร์ม เช่น

const [name, setName] = useState('');
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');

แล้วทุกครั้งที่มีการเปลี่ยนค่าฟิลด์ เราต้องใช้ onChange มาจับข้อมูลอีก

<input type="text" value={name} onChange={(e) => setName(e.target.value)} />

มันใช้ได้ก็จริง แต่มันเยอะและยุ่งยาก!

React Hook Form (RHF) ช่วยลดความยุ่งยากนี้ ด้วยการทำให้ฟอร์มเป็นแบบ uncontrolled ซึ่งหมายความว่า ไม่ต้องใช้ useState() คุมค่าฟิลด์แบบ manual แต่ให้ React ทำงานร่วมกับ DOM โดยตรง

ติดตั้ง React Hook Form

ก่อนอื่น ต้องติดตั้ง React Hook Form ก่อน

npm install react-hook-form

หรือถ้าใช้ Yarn

yarn add react-hook-form


ใช้ React Hook Form ใน Next.js

มาดูโค้ดตัวอย่างง่ายๆ ของการใช้งาน React Hook Form ใน Next.js

สร้างฟอร์มแบบง่าย

import { useForm } from 'react-hook-form';

export default function MyForm() {
  const { register, handleSubmit } = useForm();

  const onSubmit = (data) => {
    console.log(data);
  };

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <input {...register('name')} placeholder="ชื่อ" />
      <input {...register('email')} type="email" placeholder="อีเมล" />
      <button type="submit">ส่ง</button>
    </form>
  );
}

อธิบายโค้ด:

  • useForm() คือฟังก์ชันหลักที่ใช้ดึง API ต่างๆ
  • register() ใช้สำหรับเชื่อมโยงอินพุตฟอร์มกับ React Hook Form
  • handleSubmit(onSubmit) ใช้จัดการการส่งฟอร์ม
  • data ที่ได้จาก onSubmit() จะเป็นอ็อบเจ็กต์ที่รวมค่าทั้งหมดในฟอร์ม


การเพิ่ม Validation

ถ้าไม่อยากให้ผู้ใช้กรอกมั่วๆ ก็ต้องเพิ่ม Validation Rules เข้าไป โดยใช้ register()

import { useForm } from 'react-hook-form';

export default function MyForm() {
  const {
    register,
    handleSubmit,
    formState: { errors },
  } = useForm();

  const onSubmit = (data) => {
    console.log(data);
  };

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <input {...register('name', { required: 'ต้องกรอกชื่อ' })} placeholder="ชื่อ" />
      {errors.name && <p>{errors.name.message}</p>}

      <input {...register('email', { required: 'ต้องกรอกอีเมล', pattern: { value: /\S+@\S+\.\S+/, message: 'รูปแบบอีเมลไม่ถูกต้อง' } })} placeholder="อีเมล" />
      {errors.email && <p>{errors.email.message}</p>}

      <button type="submit">ส่ง</button>
    </form>
  );
}

สิ่งที่เพิ่มเข้ามา:

  • ใช้ errors เพื่อตรวจจับข้อผิดพลาดในแต่ละฟิลด์
  • ถ้ามี error จะแสดงข้อความแจ้งเตือนใต้ input


การใช้ Default Values

บางครั้งเราอาจต้องกำหนดค่าตั้งต้นให้ฟอร์ม เช่น กรณีแก้ไขข้อมูลผู้ใช้

const { register, handleSubmit } = useForm({
  defaultValues: {
    name: 'John Doe',
    email: 'john@example.com',
  },
});


ใช้ Controller กับ React Hook Form

ถ้าเราต้องใช้ React Component ของบุคคลที่สาม (เช่น React Select หรือ DatePicker) ที่ไม่รองรับ register() โดยตรง เราต้องใช้ Controller

import { useForm, Controller } from 'react-hook-form';
import Select from 'react-select';

export default function MyForm() {
  const { control, handleSubmit } = useForm();

  const onSubmit = (data) => {
    console.log(data);
  };

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <Controller
        name="gender"
        control={control}
        render={({ field }) => <Select {...field} options={[{ label: 'ชาย', value: 'male' }, { label: 'หญิง', value: 'female' }]} />}
      />
      <button type="submit">ส่ง</button>
    </form>
  );
}

ใช้งานร่วมกับ Next.js API Routes

ถ้าเราต้องการส่งข้อมูลไปที่ API ใน Next.js สามารถทำแบบนี้

const onSubmit = async (data) => {
  const response = await fetch('/api/submit', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(data),
  });
  const result = await response.json();
  console.log(result);
};

ฝั่ง API Route (/pages/api/submit.js)

export default function handler(req, res) {
  if (req.method === 'POST') {
    res.status(200).json({ message: 'ฟอร์มถูกส่งแล้ว!', data: req.body });
  } else {
    res.status(405).json({ message: 'Method Not Allowed' });
  }
}

สรุป

React Hook Form เป็นตัวช่วยที่ทำให้การจัดการฟอร์มใน Next.js ง่ายขึ้น ลดโค้ดที่ต้องเขียน และรองรับ validation แบบ built-in

  • ใช้งานง่าย – ใช้ register() เชื่อมโยงฟิลด์ได้เลย
  • ไม่ต้องใช้ useState() แยกฟิลด์ – ประหยัดโค้ด
  • รองรับ validation ในตัว – กำหนดเงื่อนไขได้ง่าย
  • ทำงานเร็วกว่าโซลูชันอื่นๆ เช่น Formik

ถ้าคุณต้องการจัดการฟอร์มใน Next.js React Hook Form คือคำตอบ!

วันอาทิตย์ที่ 30 มีนาคม พ.ศ. 2568

การใช้ Zustand หรือ Redux ใน Next.js: อะไรดีกว่ากัน?

ถ้าพูดถึงการจัดการ state ใน Next.js เชื่อว่าหลายคนต้องเคยลังเลระหว่าง Zustand กับ Redux แน่นอน เพราะทั้งสองตัวนี้ได้รับความนิยมมากและมีแนวทางการทำงานที่แตกต่างกัน วันนี้เราจะมาคุยกันแบบบ้านๆ ไม่เน้นทฤษฎีเยอะ แต่เน้นให้เข้าใจง่าย ว่าควรเลือกใช้ตัวไหนดีสำหรับโปรเจกต์ของคุณ

ทำไมต้องจัดการ State ใน Next.js?

Next.js เป็น React Framework ที่ช่วยให้เราสร้างเว็บแอปพลิเคชันได้เร็วและดีขึ้น แต่ถึงจะเทพแค่ไหน Next.js ก็ยังมีปัญหาเรื่องการจัดการ state ที่เราต้องคิดหนัก โดยเฉพาะเมื่อเว็บของเรามีหลายหน้า (multi-page) หรือมีข้อมูลที่แชร์กันระหว่างคอมโพเนนต์หลายตัว

ในกรณีที่ state ของเราซับซ้อนขึ้น การใช้ useState() หรือ useContext() อาจจะไม่พอแล้ว เช่น

  • มีหลายคอมโพเนนต์ต้องใช้ข้อมูลร่วมกัน
  • ต้องเก็บสถานะของผู้ใช้ที่ล็อกอิน
  • ต้องดึงข้อมูลจาก API แล้วแชร์ให้ทุกหน้าใช้

ถึงจุดนี้แหละ ที่พวก State Management Library อย่าง Zustand และ Redux โผล่มาช่วยชีวิตเรา

Redux: เจ้าพ่อ State Management

จุดเด่นของ Redux

Redux เป็น state container ระดับตำนานที่อยู่กับ React มานาน มันใช้ single source of truth (เก็บ state ไว้ที่เดียว) และเปลี่ยนค่า state ด้วย action และ reducer

สิ่งที่ทำให้ Redux เจ๋งคือ

  • Predictable State – ทุกการเปลี่ยนแปลงของ state ถูกควบคุมโดย action และ reducer ทำให้ debug ง่าย
  • DevTools สุดเทพ – มี Redux DevTools ให้ตรวจสอบทุกการเปลี่ยนแปลงของ state ได้แบบ real-time
  • Ecosystem ใหญ่โต – มี middleware อย่าง Redux Thunk หรือ Redux Saga ช่วยให้จัดการ asynchronous action ได้ง่ายขึ้น


จุดด้อยของ Redux

  • โค้ดยาว – Redux ต้องสร้าง action, reducer และ dispatch ซึ่งทำให้โค้ดยืดยาว ถ้าทำผิดที่เดียวอาจล้มทั้งระบบ
  • ต้องใช้ boilerplate เยอะ – แค่จะอัพเดท state ต้องผ่านหลายขั้นตอน เช่น สร้าง action → dispatch action → reducer → update store
  • Learning Curve สูง – สำหรับมือใหม่ Redux อาจทำให้ปวดหัว เพราะต้องเข้าใจโครงสร้างหลายอย่าง



Zustand: เบา สั้น และเร็ว

จุดเด่นของ Zustand

Zustand เป็น state management ที่เบาและง่ายกว่า Redux มันใช้แนวคิดของ flux pattern แต่ไม่ต้องมี action หรือ reducer ให้วุ่นวาย

สิ่งที่ทำให้ Zustand น่าสนใจคือ

  • API ใช้ง่าย – ไม่ต้องมี action หรือ reducer แค่สร้าง store แล้วใช้ได้เลย
  • ไม่มี boilerplate – Zustand ใช้ฟังก์ชันเดียวสร้าง state ได้ทันที
  • ใช้งานร่วมกับ Server Components ได้ดี – ทำให้เหมาะกับ Next.js ที่ใช้ App Router


จุดด้อยของ Zustand

  • ไม่มี DevTools ที่เทพเท่า Redux – ถ้าต้องการ debug อาจต้องใช้ console.log แทน
  • เหมาะกับโปรเจกต์ขนาดกลาง-เล็ก – ถ้าเป็นแอปขนาดใหญ่ Redux อาจจะควบคุม state ได้ดีกว่า





เปรียบเทียบ Redux vs Zustand

Feature Redux Zustand
โครงสร้างโค้ด ซับซ้อน ต้องมี action, reducer, store เรียบง่าย ใช้ฟังก์ชันเดียวสร้าง store
Performance อาจมี overhead สูงถ้าใช้ไม่ดี เบาและเร็วกว่า Redux
DevTools Redux DevTools ช่วย debug ได้ดี มี DevTools แต่ไม่เทพเท่า Redux
การใช้งานกับ Next.js ต้อง config เพิ่ม ใช้งานง่าย ไม่มี config ยุ่งยาก
เหมาะกับโปรเจกต์ ใหญ่และซับซ้อน เล็ก-กลาง


ควรใช้ Zustand หรือ Redux ใน Next.js?

ใช้ Redux ถ้า

✅ โปรเจกต์ของคุณ ซับซ้อน และต้องจัดการ state เยอะ
✅ ต้องการ DevTools เพื่อ debug state ได้ง่าย
✅ แอปของคุณต้องการ middleware เช่น Redux Thunk หรือ Redux Saga

ใช้ Zustand ถ้า

✅ คุณต้องการโค้ดที่ เรียบง่าย และ ไม่ต้องการ boilerplate
✅ โปรเจกต์ของคุณเป็น ขนาดเล็ก-กลาง และไม่ต้องการ state management ที่หนัก
✅ คุณใช้ Next.js App Router และต้องการ state management ที่เข้ากันได้ดี


ตัวอย่างการใช้ Redux กับ Next.js

ก่อนจะใช้ Redux กับ Next.js ต้องติดตั้งแพ็กเกจก่อน

npm install @reduxjs/toolkit react-redux

จากนั้นสร้าง store.js

import { configureStore, createSlice } from '@reduxjs/toolkit';

const counterSlice = createSlice({
  name: 'counter',
  initialState: { value: 0 },
  reducers: {
    increment: (state) => { state.value += 1; },
    decrement: (state) => { state.value -= 1; },
  },
});

export const { increment, decrement } = counterSlice.actions;

export const store = configureStore({
  reducer: {
    counter: counterSlice.reducer,
  },
});

แล้วใช้ Provider ใน _app.js หรือ layout.js

import { Provider } from 'react-redux';
import { store } from '../store';

export default function App({ Component, pageProps }) {
  return (
    <Provider store={store}>
      <Component {...pageProps} />
    </Provider>
  );
}

ดึงค่า state ไปใช้ในคอมโพเนนต์

import { useSelector, useDispatch } from 'react-redux';
import { increment, decrement } from '../store';

export default function Counter() {
  const count = useSelector((state) => state.counter.value);
  const dispatch = useDispatch();

  return (
    <div>
      <h1>{count}</h1>
      <button onClick={() => dispatch(increment())}>+</button>
      <button onClick={() => dispatch(decrement())}>-</button>
    </div>
  );
}

ตัวอย่างการใช้ Zustand กับ Next.js

ติดตั้ง Zustand ก่อน

npm install zustand

สร้าง store.js

import { create } from 'zustand';

const useCounterStore = create((set) => ({
  count: 0,
  increment: () => set((state) => ({ count: state.count + 1 })),
  decrement: () => set((state) => ({ count: state.count - 1 })),
}));

export default useCounterStore;

ใช้ในคอมโพเนนต์

import useCounterStore from '../store';

export default function Counter() {
  const { count, increment, decrement } = useCounterStore();

  return (
    <div>
      <h1>{count}</h1>
      <button onClick={increment}>+</button>
      <button onClick={decrement}>-</button>
    </div>
  );
}

สรุป

  • Redux เหมาะกับโปรเจกต์ขนาดใหญ่ที่ต้องการความเป็นระบบ
  • Zustand เหมาะกับโปรเจกต์ที่ต้องการความง่ายและเร็ว
  • ถ้าคุณไม่แน่ใจ ให้ลองใช้ Zustand ก่อน ถ้ารู้สึกว่าไม่พอ ค่อยย้ายไป Redux

สุดท้ายแล้ว การเลือกเครื่องมือขึ้นอยู่กับ ความเหมาะสมของโปรเจกต์ ไม่มีกฎตายตัวว่าต้องใช้ตัวไหนเสมอไป

วันเสาร์ที่ 29 มีนาคม พ.ศ. 2568

🚀 การใช้ Headless UI กับ Next.js: ยืดหยุ่นกว่า สวยกว่า ใช้งานง่าย!

อยากได้ UI สวยๆ แต่ไม่อยากติดดีไซน์เดิมๆ ใช่ไหม? 🤔
Headless UI คือทางออก! มันเป็นชุดคอมโพเนนต์ที่ ไม่มีสไตล์ตายตัว ให้คุณออกแบบเองได้เต็มที่

ในบทความนี้ เราจะมาดูว่า Headless UI คืออะไร ทำไมถึงเหมาะกับ Next.js และมีวิธีใช้งานยังไงบ้าง พร้อมตัวอย่างโค้ดที่ใช้ได้จริง! 🎯

🎭 Headless UI คืออะไร?

Headless UI เป็นชุดของ UI Components ที่พัฒนาโดยทีม Tailwind CSS ซึ่งเน้นให้ ความยืดหยุ่น และ ปรับแต่งสไตล์เองได้

🔹 ข้อแตกต่างจาก UI Library ทั่วไป

Headless UI UI Library ทั่วไป (เช่น MUI, Ant Design)
ดีไซน์ ไม่มีดีไซน์บังคับ ปรับแต่งเอง มีดีไซน์ตายตัว ต้องใช้ตามที่ Library กำหนด
ความยืดหยุ่น ใช้กับ Tailwind, CSS, Styled Components ได้หมด ต้องใช้ตามโครงสร้างที่ Library กำหนด
ประสิทธิภาพ เบา เร็ว ไม่โหลดสไตล์เกินจำเป็น อาจมี CSS หรือ JS ส่วนเกินที่ไม่ได้ใช้
การรองรับ Accessibility มีการจัดการโฟกัสและคีย์บอร์ดให้ บางตัวรองรับ แต่บางตัวอาจต้องทำเพิ่มเอง
เหมาะกับ คนที่ต้องการ Custom UI เอง คนที่ต้องการ UI พร้อมใช้

🛠 วิธีติดตั้ง Headless UI ใน Next.js

ติดตั้งผ่าน npm หรือ yarn ง่ายๆ

npm install @headlessui/react

หรือ

yarn add @headlessui/react


🎯 ตัวอย่างการใช้งาน Headless UI

🔥 1️⃣ ทำ Modal ที่ใช้จริงได้เลย

Modal หรือ Dialog เป็นสิ่งที่ใช้บ่อยในเว็บ เช่น แจ้งเตือน, ฟอร์มสมัครสมาชิก


🔹 ตัวอย่าง Modal แบบใช้ Headless UI

import { Dialog, Transition } from "@headlessui/react";
import { Fragment, useState } from "react";

export default function ExampleModal() {
  const [isOpen, setIsOpen] = useState(false);

  return (
    <div className="text-center mt-10">
      <button
        onClick={() => setIsOpen(true)}
        className="px-4 py-2 bg-blue-500 text-white rounded"
      >
        เปิด Modal
      </button>

      <Transition appear show={isOpen} as={Fragment}>
        <Dialog as="div" className="fixed inset-0 z-50 flex items-center justify-center" onClose={() => setIsOpen(false)}>
          <div className="bg-white p-6 rounded-lg shadow-lg">
            <Dialog.Title className="text-lg font-bold">นี่คือ Modal!</Dialog.Title>
            <Dialog.Description className="mt-2 text-gray-600">
              กดปุ่มปิดเพื่อออกจาก Modal
            </Dialog.Description>
            <button
              onClick={() => setIsOpen(false)}
              className="mt-4 px-4 py-2 bg-red-500 text-white rounded"
            >
              ปิด
            </button>
          </div>
        </Dialog>
      </Transition>
    </div>
  );
}

✅ ทำไม Headless UI ถึงดีกว่า Modal ปกติ?

  • ใช้งานง่าย แค่ใส่ Dialog
  • รองรับ Keyboard Accessibility อัตโนมัติ
  • ไม่มีสไตล์เกินจำเป็น 👉 Customize ได้เต็มที่!


📜 2️⃣ ทำ Dropdown Menu แบบโปร

🔹 ตัวอย่าง Dropdown Menu

import { Menu, Transition } from "@headlessui/react";
import { Fragment } from "react";

export default function Dropdown() {
  return (
    <Menu as="div" className="relative inline-block text-left">
      <Menu.Button className="px-4 py-2 bg-blue-500 text-white rounded">เมนู</Menu.Button>

      <Transition
        as={Fragment}
        enter="transition ease-out duration-100"
        enterFrom="opacity-0 scale-95"
        enterTo="opacity-100 scale-100"
        leave="transition ease-in duration-75"
        leaveFrom="opacity-100 scale-100"
        leaveTo="opacity-0 scale-95"
      >
        <Menu.Items className="absolute mt-2 w-48 bg-white border rounded shadow-lg">
          <Menu.Item>
            {({ active }) => (
              <a className={`block px-4 py-2 ${active ? "bg-gray-200" : ""}`} href="#">
                โปรไฟล์
              </a>
            )}
          </Menu.Item>
          <Menu.Item>
            {({ active }) => (
              <a className={`block px-4 py-2 ${active ? "bg-gray-200" : ""}`} href="#">
                ตั้งค่า
              </a>
            )}
          </Menu.Item>
        </Menu.Items>
      </Transition>
    </Menu>
  );
}

✅ Dropdown ของ Headless UI ดียังไง?

  • ปรับแต่งได้ง่าย ไม่มีสไตล์บังคับ
  • รองรับ Keyboard Navigation
  • Animate ได้ Smooth (แค่ใช้ Transition)


🏁 สรุป

หัวข้อ รายละเอียด
Headless UI คืออะไร? ชุดคอมโพเนนต์ที่ไม่มีสไตล์บังคับ ให้เราปรับแต่งได้เอง
ข้อดี ยืดหยุ่น, ใช้กับ Tailwind CSS ได้ดี, เร็วและเบากว่า UI Library อื่นๆ
การติดตั้ง npm install @headlessui/react
คอมโพเนนต์ที่ใช้งานบ่อย Modal (Dialog), Dropdown (Menu), Tabs, Popover
เหมาะกับใคร? คนที่ต้องการ UI ที่ Custom ได้เต็มที่ โดยไม่อยากติดดีไซน์เดิมๆ

🎯 สรุปง่ายๆ

ถ้าคุณอยากให้เว็บมี UI ที่ยืดหยุ่น ไม่ถูกบังคับด้วยดีไซน์เดิมๆ และรองรับ Accessibility ดีเยี่ยม 👉 ใช้ Headless UI เถอะ!

ลองไปใช้กับ Tailwind CSS แล้วคุณจะหลงรัก 💙

วันศุกร์ที่ 28 มีนาคม พ.ศ. 2568

🌙 การทำ Dark Mode ใน Next.js: ให้เว็บของคุณเท่ขึ้นแบบมืออาชีพ!

เบื่อไหม? กับเว็บที่สว่างจ้าจนตาล้า 😵‍💫 ถ้าคุณอยากให้ผู้ใช้เลือกได้ว่าอยากใช้ โหมดสว่าง (Light Mode) หรือ โหมดมืด (Dark Mode) ใน Next.js เรามีวิธีง่ายๆ ให้คุณลองทำ!

ในบทความนี้ เราจะมาเจาะลึก การทำ Dark Mode ใน Next.js แบบครบทุกวิธี ทั้งใช้ CSS Variables, useState, localStorage และไลบรารีเสริม เช่น Next Themes กับ Tailwind CSS พร้อมตัวอย่างโค้ดที่เอาไปใช้ได้เลย! 🚀


🔥 ทำไมต้องมี Dark Mode?

✅ ข้อดีของ Dark Mode

  • ช่วยลดอาการล้าสายตา 👀 (สำหรับคนที่ใช้เว็บตอนกลางคืน)
  • ประหยัดแบตเตอรี่ 🔋 (โดยเฉพาะจอ OLED)
  • ดูเท่! 😎 หลายเว็บใหญ่ๆ ก็ใช้ เช่น YouTube, Twitter


🛠 วิธีทำ Dark Mode ใน Next.js

🚀 1. ใช้ CSS Variables + useState

วิธีนี้เหมาะกับ:
✅ เว็บที่ไม่ซับซ้อนมาก
✅ ไม่ต้องใช้ไลบรารีเสริม


👨‍💻 ขั้นตอนการทำ

1️⃣ สร้าง globals.css และกำหนด Theme

แก้ไขไฟล์ styles/globals.css

:root {
  --bg-color: #ffffff;
  --text-color: #000000;
}

.dark-mode {
  --bg-color: #121212;
  --text-color: #ffffff;
}

body {
  background-color: var(--bg-color);
  color: var(--text-color);
  transition: background 0.3s, color 0.3s;
}

2️⃣ เพิ่มปุ่มสลับโหมดใน index.js

เปิดไฟล์ pages/index.js แล้วเพิ่มโค้ดนี้

import { useState } from "react";

export default function Home() {
  const [darkMode, setDarkMode] = useState(false);

  const toggleDarkMode = () => {
    setDarkMode(!darkMode);
    document.body.classList.toggle("dark-mode");
  };

  return (
    <div style={{ textAlign: "center", marginTop: "50px" }}>
      <h1>Dark Mode in Next.js 🌙</h1>
      <button
        onClick={toggleDarkMode}
        style={{
          padding: "10px 20px",
          fontSize: "18px",
          cursor: "pointer",
          background: darkMode ? "#fff" : "#333",
          color: darkMode ? "#000" : "#fff",
          border: "none",
          borderRadius: "5px",
        }}
      >
        {darkMode ? "Switch to Light Mode ☀️" : "Switch to Dark Mode 🌙"}
      </button>
    </div>
  );
}

🛠 2. ใช้ localStorage จำค่าโหมด

ปัญหา: ถ้ารีเฟรชหน้าเว็บ ค่าโหมดที่เลือกไว้จะหายไป
ทางแก้: ใช้ localStorage จำค่าการตั้งค่าของผู้ใช้

อัปเดตโค้ด index.js

import { useState, useEffect } from "react";

export default function Home() {
  const [darkMode, setDarkMode] = useState(false);

  useEffect(() => {
    const savedTheme = localStorage.getItem("theme");
    if (savedTheme === "dark") {
      setDarkMode(true);
      document.body.classList.add("dark-mode");
    }
  }, []);

  const toggleDarkMode = () => {
    setDarkMode(!darkMode);
    if (!darkMode) {
      document.body.classList.add("dark-mode");
      localStorage.setItem("theme", "dark");
    } else {
      document.body.classList.remove("dark-mode");
      localStorage.setItem("theme", "light");
    }
  };

  return (
    <div style={{ textAlign: "center", marginTop: "50px" }}>
      <h1>Dark Mode in Next.js 🌙</h1>
      <button
        onClick={toggleDarkMode}
        style={{
          padding: "10px 20px",
          fontSize: "18px",
          cursor: "pointer",
          background: darkMode ? "#fff" : "#333",
          color: darkMode ? "#000" : "#fff",
          border: "none",
          borderRadius: "5px",
        }}
      >
        {darkMode ? "Switch to Light Mode ☀️" : "Switch to Dark Mode 🌙"}
      </button>
    </div>
  );
}

✅ ข้อดี:

  • จำค่าธีมได้หลังจากรีเฟรช


🎨 3. ใช้ next-themes (แนะนำ!)

✅ ข้อดีของ next-themes

  • ใช้ง่ายมาก (ไม่ต้องเขียนโค้ดเยอะ)
  • รองรับ ระบบ Dark Mode อัตโนมัติจาก OS



🛠 ติดตั้ง next-themes

npm install next-themes


👨‍💻 ใช้งาน next-themes

1️⃣ แก้ไข _app.js

import { ThemeProvider } from "next-themes";

export default function MyApp({ Component, pageProps }) {
  return (
    <ThemeProvider attribute="class">
      <Component {...pageProps} />
    </ThemeProvider>
  );
}

2️⃣ แก้ไข index.js

import { useTheme } from "next-themes";
import { useEffect, useState } from "react";

export default function Home() {
  const { theme, setTheme } = useTheme();
  const [mounted, setMounted] = useState(false);

  useEffect(() => setMounted(true), []);

  if (!mounted) return null; // ป้องกัน Hydration Error

  return (
    <div className="p-5 text-center">
      <h1 className="text-2xl">Dark Mode with next-themes 🌙</h1>
      <button
        onClick={() => setTheme(theme === "dark" ? "light" : "dark")}
        className="mt-3 px-4 py-2 text-white bg-black rounded"
      >
        {theme === "dark" ? "Switch to Light Mode ☀️" : "Switch to Dark Mode 🌙"}
      </button>
    </div>
  );
}

🔥 ง่ายสุดๆ แค่เรียก setTheme() แล้วปล่อยให้ next-themes จัดการให้!

🏁 สรุป

วิธี ข้อดี ข้อเสีย
CSS Variables + useState ง่าย, ไม่ต้องใช้ไลบรารี รีเฟรชแล้วค่าหาย
localStorage จำค่าธีมได้หลังรีเฟรช ต้องใช้ useEffect
next-themes (แนะนำ) รองรับ Dark Mode อัตโนมัติ ต้องติดตั้งไลบรารี

🎯 ถ้าคุณอยากทำ Dark Mode แบบง่ายสุด 👉 ใช้ next-themes
🎯 ถ้าอยากควบคุมเองทั้งหมด 👉 ใช้ CSS Variables + localStorage

🚀 ลองทำเลย แล้วเว็บของคุณจะดูโปรขึ้น 300%!! 😆

วันพฤหัสบดีที่ 27 มีนาคม พ.ศ. 2568

การใช้ Framer Motion กับ Next.js: เพิ่ม Animation ให้เว็บดูโปรแบบมืออาชีพ 🚀

อย่าปล่อยให้เว็บนิ่งเหมือน PowerPoint สไลด์แรก 😆 ถ้าอยากให้เว็บของคุณดู Smooth และมีชีวิตชีวา Framer Motion คือทางออก!

ในบทความนี้ เราจะมาดูกันว่า Framer Motion ทำงานยังไง และเราจะใช้มันเพิ่ม Animation ให้กับ Next.js ได้แบบไหนบ้าง พร้อมตัวอย่างโค้ดที่เอาไปใช้ได้เลย!


🎭 Framer Motion คืออะไร?

Framer Motion เป็นไลบรารีสำหรับทำ Animation บน React และ Next.js มันใช้ง่ายมาก และทำให้เว็บดู Smooth & Interactive โดยไม่ต้องเขียน CSS Animation เอง

✅ ข้อดีของ Framer Motion

  • ใช้ Props-based Animation 👉 ไม่ต้องเขียน @keyframes
  • รองรับ Server-side Rendering (SSR) 👉 ใช้กับ Next.js ได้ดี
  • มี Gesture & Scroll Animation 👉 รองรับการลาก, สัมผัส และเลื่อน
  • ทำ Animation Complex ได้ง่ายมาก


🚀 ติดตั้ง Framer Motion ใน Next.js

เริ่มต้นง่ายๆ แค่ติดตั้งไลบรารีก่อน

npm install framer-motion

จากนั้นก็พร้อมใช้งานได้เลย! 🎉


🔥 เริ่มต้นกับ Motion Components

🎬 1️⃣ สร้าง Animation ง่ายๆ

เปิดไฟล์ pages/index.js แล้วลองใช้ Motion Component

import { motion } from "framer-motion";

export default function Home() {
  return (
    <motion.div
      initial={{ opacity: 0 }}
      animate={{ opacity: 1 }}
      transition={{ duration: 1 }}
      style={{ fontSize: "24px", textAlign: "center", marginTop: "50px" }}
    >
      Hello Framer Motion! 👋
    </motion.div>
  );
}

🔹 อธิบายโค้ด:

  • initial={{ opacity: 0 }} 👉 เริ่มต้นที่โปร่งใส (มองไม่เห็น)
  • animate={{ opacity: 1 }} 👉 ค่อยๆ เผยตัวออกมา
  • transition={{ duration: 1 }} 👉 ใช้เวลา 1 วินาที

🎉 ได้ Animation Fade-in แบบง่ายๆ แล้ว!



🏃 เพิ่ม Motion ให้ปุ่ม

🎬 2️⃣ ปุ่มที่เด้งตอนกด

import { motion } from "framer-motion";

export default function Button() {
  return (
    <motion.button
      whileTap={{ scale: 0.9 }}
      whileHover={{ scale: 1.1 }}
      style={{
        padding: "10px 20px",
        fontSize: "18px",
        borderRadius: "5px",
        border: "none",
        background: "blue",
        color: "white",
        cursor: "pointer",
      }}
    >
      Click Me 🚀
    </motion.button>
  );
}

🔹 อธิบายโค้ด:

  • whileTap={{ scale: 0.9 }} 👉 ปุ่มย่อเล็กลงนิดนึงตอนกด
  • whileHover={{ scale: 1.1 }} 👉 ปุ่มขยายขึ้นตอนเอาเมาส์ไปชี้

🛠 ปรับแต่งปุ่มให้ดูโปรได้ง่ายๆ แบบนี้เลย!


🔄 Animate หลายองค์ประกอบพร้อมกัน

🎬 3️⃣ ทำ List ที่มี Animation ตอนโหลด

import { motion } from "framer-motion";

const items = ["🍎 Apple", "🍌 Banana", "🍒 Cherry", "🍇 Grape"];

export default function List() {
  return (
    <div style={{ padding: "20px" }}>
      {items.map((item, index) => (
        <motion.div
          key={item}
          initial={{ opacity: 0, x: -50 }}
          animate={{ opacity: 1, x: 0 }}
          transition={{ duration: 0.5, delay: index * 0.2 }}
          style={{ fontSize: "20px", marginBottom: "10px" }}
        >
          {item}
        </motion.div>
      ))}
    </div>
  );
}

🔹 อธิบายโค้ด:

  • x: -50 👉 แต่ละไอเทมเริ่มจากซ้ายมือแล้วเลื่อนเข้ามา
  • delay: index * 0.2 👉 ไอเทมจะเลื่อนเข้ามาทีละตัว (ไม่มาพร้อมกัน)

📜 ได้ List ที่มี Animation นุ่มๆ แล้ว!



🎡 ใช้ Variants ให้ Animation ดูเป็นธรรมชาติ

ถ้าต้องการทำ Animation ที่มีหลายสถานะ ใช้ Variants จะช่วยให้โค้ดดูสะอาดขึ้น

🎬 4️⃣ ทำ Animation ตอนเปลี่ยนหน้าใน Next.js

import { motion } from "framer-motion";

const pageVariants = {
  hidden: { opacity: 0, y: 50 },
  visible: { opacity: 1, y: 0, transition: { duration: 0.5 } },
};

export default function Page() {
  return (
    <motion.div
      initial="hidden"
      animate="visible"
      variants={pageVariants}
      style={{ fontSize: "24px", textAlign: "center", marginTop: "50px" }}
    >
      Welcome to the new page! 🎉
    </motion.div>
  );
}

🔹 อธิบายโค้ด:

  • hidden 👉 เริ่มต้นจากด้านล่างและโปร่งใส
  • visible 👉 ขึ้นมาที่ตำแหน่งปกติ และค่อยๆ แสดงผล



📜 ทำ Scroll Animation

อยากให้ Element ขยับตอนเลื่อนหน้าจอไหม?

🎬 5️⃣ ทำให้ Element โผล่มาตอน Scroll

import { motion, useScroll } from "framer-motion";

export default function ScrollEffect() {
  const { scrollYProgress } = useScroll();

  return (
    <motion.div
      style={{
        scale: scrollYProgress,
        width: "100px",
        height: "100px",
        background: "red",
        margin: "50px auto",
      }}
    />
  );
}

🔹 อธิบายโค้ด:

  • useScroll() 👉 ใช้ตรวจจับการเลื่อน
  • scale: scrollYProgress 👉 ยิ่งเลื่อนลง Element ยิ่งขยายใหญ่

🔥 ลองเลื่อนหน้าจอดู แล้วจะเห็นความเปลี่ยนแปลง!


🏁 สรุป

  • Framer Motion ทำให้ Animation ใน Next.js ง่ายและดู Smooth
  • ใช้ motion.div เพื่อทำ Animation ได้ทันที
  • whileHover & whileTap ทำให้ปุ่ม Interactive
  • Variants ช่วยจัดการหลายสถานะของ Animation
  • useScroll ใช้ทำ Scroll Animation ได้ง่าย

🎨 ลองเล่นดู แล้วเว็บของคุณจะดูแพงขึ้น 300%!! 😆

วันพุธที่ 26 มีนาคม พ.ศ. 2568

การใช้ Component Library ใน Next.js: เลือก Chakra UI, MUI หรือ shadcn/ui ดี?


ถ้าไม่อยากเขียน CSS เอง ให้ Component Library จัดการให้เถอะ!

การทำเว็บให้ดูดี ไม่ใช่แค่โค้ดดี แต่ต้องมี UI ที่สวยด้วย แต่การเขียน CSS เองมันก็เหนื่อยใช่ไหม? 😵‍💫 ถ้างั้นลองใช้ Component Library ที่มี UI สวยๆ ให้ใช้เลยดีกว่า

ในบทความนี้ เราจะมาแนะนำ 3 ตัวเลือกยอดนิยมสำหรับ Next.js ได้แก่ Chakra UI, MUI และ shadcn/ui พร้อมตัวอย่างโค้ดให้คุณลองใช้เลย

🚀 Component Library คืออะไร?

Component Library คือชุดของ UI Components (เช่น ปุ่ม, ฟอร์ม, การ์ด) ที่ออกแบบมาพร้อมใช้งาน ช่วยให้เรา ไม่ต้องเขียน CSS เอง และยังได้ UX ที่ดีขึ้น

✅ ทำไมต้องใช้ Component Library?

  • ลดเวลาทำงาน 👉 ไม่ต้องสร้าง UI จากศูนย์
  • รองรับ Responsive 👉 ใช้ได้ทั้งมือถือและเดสก์ท็อป
  • เข้ากันได้ดีกับ Next.js 👉 โหลดเร็ว, รองรับ SSR


Chakra UI – UI ที่ใช้ง่ายสุดๆ 😍

🔹 ข้อดีของ Chakra UI

✅ ใช้ Props-based Styling (ไม่ต้องเขียน CSS)
✅ รองรับ Dark Mode อัตโนมัติ
✅ มี Flexbox & Grid พร้อมใช้

🛠 ติดตั้ง Chakra UI

npm install @chakra-ui/react @emotion/react @emotion/styled framer-motion

🌟 ใช้ Chakra UI ใน Next.js

1️⃣ ห่อแอปด้วย <ChakraProvider>

แก้ไข pages/_app.js

import { ChakraProvider } from "@chakra-ui/react";

export default function MyApp({ Component, pageProps }) {
  return (
    <ChakraProvider>
      <Component {...pageProps} />
    </ChakraProvider>
  );
}


2️⃣ ลองใช้ปุ่มจาก Chakra UI

สร้าง pages/index.js

import { Button, Box, Text } from "@chakra-ui/react";

export default function Home() {
  return (
    <Box p={5}>
      <Text fontSize="xl">Hello Chakra UI! 👋</Text>
      <Button colorScheme="blue" mt={3}>
        Click Me
      </Button>
    </Box>
  );
}

🔥 จุดเด่น:

  • colorScheme="blue" 👉 เปลี่ยนสีปุ่มง่ายๆ
  • Box 👉 ใช้แทน <div> และมี spacing props เช่น p={5}



MUI (Material UI) – UI มาตรฐานองค์กร 🏢

🔹 ข้อดีของ MUI

✅ ดีไซน์มาตรฐาน Google Material Design
✅ คอมโพเนนต์เยอะมาก (Button, Table, Modal ฯลฯ)
✅ มี Theme System ปรับแต่งได้

🛠 ติดตั้ง MUI

npm install @mui/material @emotion/react @emotion/styled

🌟 ใช้ MUI ใน Next.js

1️⃣ ห่อแอปด้วย <ThemeProvider>

แก้ไข pages/_app.js

import { ThemeProvider, createTheme } from "@mui/material/styles";

const theme = createTheme();

export default function MyApp({ Component, pageProps }) {
  return (
    <ThemeProvider theme={theme}>
      <Component {...pageProps} />
    </ThemeProvider>
  );
}

2️⃣ ลองใช้ปุ่มจาก MUI

สร้าง pages/index.js

import { Button, Container, Typography } from "@mui/material";

export default function Home() {
  return (
    <Container>
      <Typography variant="h4" gutterBottom>
        Welcome to MUI 🚀
      </Typography>
      <Button variant="contained" color="primary">
        Click Me
      </Button>
    </Container>
  );
}

🔥 จุดเด่น:

  • variant="contained" 👉 ปุ่มแบบพื้นหลังทึบ
  • Typography 👉 ใช้แทน <h1> หรือ <p>


shadcn/ui – UI ที่เบาและยืดหยุ่นสุดๆ 😎

🔹 ข้อดีของ shadcn/ui

✅ ใช้ Tailwind CSS (แต่งสไตล์ง่าย)
✅ ไม่มี JS Runtime (เร็วมาก)
✅ คัดลอกโค้ดมาใช้ได้เลย

🛠 ติดตั้ง shadcn/ui

npx shadcn-ui@latest init

จากนั้นเลือก "Install components" เพื่อนำคอมโพเนนต์มาใช้

🌟 ใช้ shadcn/ui ใน Next.js

1️⃣ ติดตั้ง Button

npx shadcn-ui@latest add button

2️⃣ ลองใช้ปุ่มจาก shadcn/ui

สร้าง pages/index.js

import { Button } from "@/components/ui/button";

export default function Home() {
  return (
    <div className="p-5">
      <h1 className="text-xl font-bold">Hello shadcn/ui!</h1>
      <Button className="mt-3">Click Me</Button>
    </div>
  );
}

🔥 จุดเด่น:

  • ใช้ Tailwind ได้เลย (className="text-xl")
  • ปรับแต่งสไตล์เองได้เต็มที่


🤔 สรุป: ควรเลือกตัวไหนดี?

Library เหมาะกับใคร?
Chakra UI ถ้าต้องการ Props-based Styling และใช้ง่าย
MUI ถ้าต้องการ Material Design และคอมโพเนนต์เยอะ
shadcn/ui ถ้าต้องการ UI เร็ว + ใช้ Tailwind

👉 ถ้าคุณชอบ:

  • ความง่าย + Dark Mode อัตโนมัติ 👉 Chakra UI
  • คอมโพเนนต์เยอะ + สไตล์องค์กร 👉 MUI
  • ใช้ Tailwind + เร็วที่สุด 👉 shadcn/ui


🎉 สรุป

  • Component Library ช่วยให้ทำเว็บเร็วขึ้น
  • Chakra UI ใช้ง่ายสุด ปรับแต่งสะดวก
  • MUI เหมาะกับแอปองค์กร
  • shadcn/ui เบา เร็ว ใช้ Tailwind ได้เต็มที่

ลองใช้ดู แล้วจะติดใจ! 😉

วันอังคารที่ 25 มีนาคม พ.ศ. 2568

วิธีป้องกันหน้าเว็บที่ต้องล็อกอินก่อนเข้าใช้งานใน Next.js

อย่าปล่อยให้ใครเข้ามาเที่ยวเว็บเราฟรีๆ! 😆

ถ้าคุณทำเว็บที่มีหน้า Dashboard, Profile หรือหน้าข้อมูลสำคัญ คุณคงไม่อยากให้ใครก็ได้เข้าถึงข้อมูลพวกนั้นได้ง่ายๆ เราต้องล็อกอินก่อนถึงจะเข้าได้!

ในบทความนี้ เราจะมาดูกันว่า Next.js + NextAuth.js ใช้ทำ Protected Route (หน้าที่ต้องล็อกอินก่อนเข้า) ได้ยังไง

🚀 ทำไมต้องป้องกันหน้าเว็บ?

✅ ป้องกันข้อมูลสำคัญ

  • ถ้าเว็บมีข้อมูลผู้ใช้ ห้ามให้คนที่ไม่ได้ล็อกอินเข้าถึง

✅ เพิ่มความปลอดภัย

  • ไม่เปิดช่องให้ Bot หรือ Hacker แอบเข้ามาดูข้อมูล

✅ UX ดีขึ้น

  •     ถ้าไม่ได้ล็อกอิน 👉 Redirect ไปหน้า Login
  •     ล็อกอินแล้ว 👉 เข้าใช้งานได้ปกติ

สรุป: ทำให้เว็บ ปลอดภัย + เป็นระเบียบ + ใช้งานง่าย


🔥 วิธีทำ Protected Route ใน Next.js

1️⃣ ติดตั้ง NextAuth.js

ถ้ายังไม่มี NextAuth.js ให้ติดตั้งก่อน

npm install next-auth

2️⃣ ตั้งค่า NextAuth.js

สร้างไฟล์ pages/api/auth/[...nextauth].js

import NextAuth from "next-auth";
import GoogleProvider from "next-auth/providers/google";

export default NextAuth({
  providers: [
    GoogleProvider({
      clientId: process.env.GOOGLE_CLIENT_ID,
      clientSecret: process.env.GOOGLE_CLIENT_SECRET,
    }),
  ],
  secret: process.env.NEXTAUTH_SECRET,
});

💡 Tip: เราใช้ Google Login แต่คุณเปลี่ยนไปใช้ Auth0, GitHub, หรือ Email/Password ได้

3️⃣ ตั้งค่า ENV

สร้างไฟล์ .env.local แล้วใส่ค่า

GOOGLE_CLIENT_ID=your-google-client-id
GOOGLE_CLIENT_SECRET=your-google-client-secret
NEXTAUTH_SECRET=your-random-secret
NEXTAUTH_URL=http://localhost:3000


🛑 ป้องกันหน้าที่ต้องล็อกอิน

❌ วิธีที่ 1: เช็กใน Component

import { useSession, signIn } from "next-auth/react";

export default function Dashboard() {
  const { data: session, status } = useSession();

  if (status === "loading") return <p>Loading...</p>;
  if (!session) return <button onClick={() => signIn()}>Login to access</button>;

  return <h1>Welcome to your Dashboard, {session.user.name}!</h1>;
}

🔹 อธิบายโค้ด:
  •     ถ้ากำลังโหลด ให้แสดง Loading
  •     ถ้าไม่ได้ล็อกอิน ให้แสดงปุ่ม Login
  •     ถ้าล็อกอินแล้ว แสดงข้อมูลได้ปกติ

🚀 วิธีที่ 2: Redirect ไปหน้า Login อัตโนมัติ

ใช้ getServerSideProps เช็กสิทธิ์ก่อนโหลดหน้า

import { getSession } from "next-auth/react";

export async function getServerSideProps(context) {
  const session = await getSession(context);

  if (!session) {
    return {
      redirect: {
        destination: "/login",
        permanent: false,
      },
    };
  }

  return {
    props: { session },
  };
}

export default function Dashboard({ session }) {
  return <h1>Welcome, {session.user.name}!</h1>;
}

🔹 อธิบายโค้ด:
  •     ถ้า ไม่ได้ล็อกอิน 👉 Redirect ไป /login
  •     ถ้า ล็อกอินแล้ว 👉 ให้เข้า Dashboard ได้

✅ ข้อดี:
  •     ป้องกันตั้งแต่ฝั่งเซิร์ฟเวอร์
  •     SEO Friendly เพราะไม่ได้บล็อกการโหลดหน้า

🔒 วิธีที่ 3: สร้าง Middleware ป้องกันทุกหน้า

ใช้ middleware.ts เพื่อบังคับให้ล็อกอินทุกครั้งที่เข้า /dashboard

สร้างไฟล์ middleware.ts

import { NextRequest, NextResponse } from "next/server";

export function middleware(req: NextRequest) {
  const token = req.cookies.get("next-auth.session-token");

  if (!token) {
    return NextResponse.redirect(new URL("/login", req.url));
  }

  return NextResponse.next();
}

export const config = {
  matcher: "/dashboard/:path*",
};

🔹 อธิบายโค้ด:
  •     ถ้าไม่มี Token 👉 Redirect ไป /login
  •     ถ้ามี Token 👉 อนุญาตให้เข้า /dashboard ได้

✅ ข้อดี:
  •     ป้องกันทุกหน้าใน /dashboard/ อัตโนมัติ
  •     ไม่ต้องใส่โค้ดป้องกันทุกไฟล์

🎨 สร้างหน้า Login

สร้าง pages/login.js

import { signIn } from "next-auth/react";

export default function Login() {
  return (
    <div>
      <h1>Login</h1>
      <button onClick={() => signIn("google")}>Login with Google</button>
    </div>
  );
}

🔹 อธิบายโค้ด:
  •     ปุ่มเดียวจบ! กดแล้วล็อกอินผ่าน Google


❓ คำถามที่พบบ่อย

1️⃣ ควรใช้วิธีไหนดีที่สุด?

ขึ้นอยู่กับโปรเจ็กต์:
  •     หน้าเดียวกัน (Client-side protection) 👉 useSession()
  •     ป้องกันทุกหน้า (Server-side protection) 👉 getServerSideProps()
  •     ป้องกันโฟลเดอร์ (Global protection) 👉 middleware.ts

2️⃣ NextAuth.js รองรับอะไรบ้าง?

  •     Google, Facebook, GitHub, Apple, Twitter
  •     Email/Password
  •     Magic Link

3️⃣ ถ้าอยากให้บางหน้าล็อกอิน แต่บางหน้าไม่ต้องทำไง?

  •     ใช้ Middleware เฉพาะโฟลเดอร์
  •     เช็ก useSession() ใน Component

🎉 สรุป

  •     ป้องกันหน้าเว็บที่ต้องล็อกอินก่อนเข้าใช้งาน ได้หลายวิธี
  •     ใช้ useSession() ถ้าต้องการป้องกันจากฝั่ง Client
  •     ใช้ getServerSideProps() ถ้าต้องการ Redirect อัตโนมัติ
  •     ใช้ middleware.ts ถ้าต้องการป้องกันทั้งโฟลเดอร์
ลองทำดู แล้วจะติดใจ! 😉

การใช้ Image Optimization ใน Next.js: โหลดไว SEO ดี ประหยัดแบนด์วิธ!

เคยเจอปัญหาเว็บโหลดช้าเพราะรูปภาพขนาดใหญ่หรือเปล่า? 📷 ถ้าใช่ Next.js มีฟีเจอร์ Image Optimization ที่ช่วยให้รูปภาพโหลดเร็วขึ้น อัตโนมัติ ไม...