상세 컨텐츠

본문 제목

React(리액트) 티키타카 38 (19버젼에 추가된 form action)

React

by e7e 2025. 12. 26. 11:17

본문

프로젝트는 vite로 알아서 맹글기

 

보통은 특별히 react-hook-form(인기 짱) 같은 라이브러리를 사용하지 않고, 직접 

form을 제어한다면 아마도 아래와 같은 형태가 될 꺼이당.

form 태그에 onSubmit이벤트와 ref 걸어서 사용

[ 물론 누군가는 useRef를 쓰지 않고 controlled 방식을 선호할 수도 있음이당.

  또 누군가는 ref없이 event.target을 쓸 수도 있당 ]

 

App.jsx

import { useRef, useState } from "react";

function App() {
  const [data] = useState({ email: "", password: "" });
  const formRef = useRef();

  const handleSubmit = async (e) => {
    e.preventDefault();

    const formData = new FormData(formRef.current);

    const sendData = {
      email: formData.get("email"),
      password: formData.get("password"),
    };

    // 눈으로 확인해 보아요
    console.log("sendData", sendData);

    /*
       보통 비동기  AJAX 코드,
    */

    // reset은 필요하다면....
    formRef.current.reset();
  };

  return (
    <div
      style={{
        display: "flex",
        justifyContent: "center",
      }}
    >
      <form ref={formRef} onSubmit={handleSubmit}>
        <div>
          <label htmlFor="email">이메일</label>
          <input type="email" name="email" defaultValue={""} />
        </div>
        <div>
          <label htmlFor="password">아암호</label>
          <input
            type="password"
            autoComplete={"autoComplete"}
            name="password"
            defaultValue={""}
          />
        </div>
        <div style={{ textAlign: "center", marginTop: 5 }}>
          <button>떤송 할랑? 그람눌렁</button>
        </div>
      </form>
    </div>
  );
}

export default App;

실행해서 console 창을 확인한다면 잘 됨을 느끼하게 느낀당.

 

React 19버젼에서 action을 사용하는 것이  새로 추가 되었는데,

오늘은 그거에 집중해서 괘니 한번 보도록 하장. (위 소스는 사실 이걸 염두에 두었당)

사용할 지 안 할지는 항상 당신의 선택이당.(상황을 근거로 장단점 파악이 기준)

action을 넣어서 수정하면 아래와 같은 소스가 된당.

 

App.jsx

import { useState } from "react";

function App() {

  const myAction = async (formData) => {
    const sendData = {
      email: formData.get("email"),
      password: formData.get("password"),
    };
    // 눈으로 확인해 보아요
    console.log("sendData", sendData);

    /*
       보통 비동기  AJAX 코드,
    */
  };

  return (
    <div
      style={{
        display: "flex",
        justifyContent: "center",
      }}
    >
      <form action={myAction}>
        <div>
          <label htmlFor="email">이메일</label>
          <input type="email" name="email" defaultValue={""} />
        </div>
        <div>
          <label htmlFor="password">아암호</label>
          <input
            type="password"
            autoComplete={"autoComplete"}
            name="password"
            defaultValue={""}
          />
        </div>
        <div style={{ textAlign: "center", marginTop: 5 }}>
          <button>떤송 할랑? 그람눌렁</button>
        </div>
      </form>
    </div>
  );
}

export default App;

 

낯설지만 살짝 시간을 들여서 보면 느끼미가 바로 연락을 주었을 거시당.

action을 쓰면 자동으로 매개변수로 formData를 넘겨줘서 

직접 formData = new FormData(폼-ref)를 하지 않아도 된당.

쪼메  더 편해진 것이당.

 

추가적으로 제공해주는 훅(Hook)이 있는데, 그것도 써 보장.

[ useActionState당 ]

App.jsx

import { useActionState, useState } from "react";

// 비동기 딜레이 페이크 코드
async function delay(timeSec) {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve();
    }, timeSec * 1000);
  });
}

function App() {
  const [data, setData] = useState({ email: "", password: "" });

  const myAction = async (prevState, formData) => {
    console.log("체킁킁: ", prevState);
    const email = formData.get("email");

    if (!email || email.trim === "") {
      return {
        ...prevState,
        error: {
          email: "email은 필수닝 적어주삼",
        },
      };
    }

    const password = formData.get("password");
    if (!password || password.trim === "") {
      return {
        ...prevState,
        error: {
          password: "password없이 어쩔거얌",
        },
      };
    }

    // 무언가 필요한 일 하겠징, 그게 비동기? 그래서 가짜롱
    await delay(5);

    console.log("출력:", data);

    return {
      error: null,
    };
  };

  const [formState, formAction, pending] = useActionState(myAction, {
    error: null,
  });

  const handleEmail = (e) => {
    setData((prev) => ({ ...prev, email: e.target.value }));
  };

  const handlePassword = (e) => {
    setData((prev) => ({ ...prev, password: e.target.value }));
  };

  return (
    <div
      style={{
        display: "flex",
        justifyContent: "center",
      }}
    >
      <form action={formAction}>
        <div>
          <label htmlFor="email">이메일</label>
          <input
            type="email"
            autoComplete="email"
            name="email"
            value={data.email}
            onChange={handleEmail}
          />
          {formState.error && formState.error.email && (
            <p style={{ color: "red" }}>{formState.error.email}</p>
          )}
        </div>
        <div>
          <label htmlFor="password">아암호</label>
          <input
            type="password"
            autoComplete={"autoComplete"}
            name="password"
            value={data.password}
            onChange={handlePassword}
          />
          {formState.error && formState.error.password && (
            <p style={{ color: "red" }}>{formState.error.password}</p>
          )}
        </div>
        <div style={{ textAlign: "center", marginTop: 5 }}>
          <button disabled={pending}>
            {pending ? "지둘령" : "떤송 할랑? 그람눌렁"}
          </button>
        </div>
      </form>
    </div>
  );
}

export default App;

 

Hook 사용법은 아래와 같당. 처음에만 형태가 낯설 수 있당.

useReducer를 써본 사람이라면 음 비슷하군 하는 느끼미가 왔을거당.

Hook을 이것 저것 많이 써보면 대부분이 몇개 패턴 안에서 비슷하다는 느끼미가 온당

useActionState 훅 함수의 두번째 매개변수가 초기값이당. (어떤 형태등 상관없당)

지금은 error를 담으려 error 속성을 주고 초기값 null을 주었당.

  const [formState, formAction, pending] = useActionState(myAction, {   error: null,});

 

첫번째 매개변수인 콜백함수 myAction은 직접 만들어야 하는데,

myAction 함수의 매개변수에 이전 상태를 가지는 변수가 1개 추가됨을 인지하장.

 const myAction = async (prevState, formData) => {

 

form 태그의 action에는 myAction이 아니공 

useActionState에서 구조분해 해온 formAction이 들어간당.

     <form action={formAction}>

 

구조 분해로 꺼낸 pending은 동작 중인지, 동작이 끝났는지를 알려준당.

안 쓸 이유가 없당. 소스를 보자마자 음 유용하군! 느끼미가 온당.

          <button disabled={pending}>
            {pending ? "지둘령" : "떤송 할랑? 그람눌렁"}
          </button>

 

요기까지의 형태만 샘플로 가지고 있으면, 필요한 부분만 수정하는 형태로 사용가능하당.

나머지는 코드를 보면 이해가 될 꺼이당.

 

실행 결과는 아래와 같은 흐름이 아니겠는강.

설명만 길었지 써 보면 당황되지 않고 쉬움이당.

 

구지 억지로  1개 더 해본다면  useFormStatus 훅도 있는데, 한번 보장

[ 쓰지 않는 다고 아무도 머라 안한당. 사실 위에 껄로 충분하당, 취향 차? ]

form 태그 안의 button 사용된 부분만 별도로 아래 처럼 맹근당.

function E7ESubmit() {
  const { pending } = useFormStatus();
  return (
    <button disabled={pending}>{pending ? "지둘령" : "경미니? 미누니?"}</button>
  );
}

 

이제 전체 소스는 아래와 같을 지어당.

import { useActionState, useState } from "react";
import { useFormStatus } from "react-dom";

// 비동기 딜레이 페이크 코드
async function delay(timeSec) {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve();
    }, timeSec * 1000);
  });
}

// 별도 submit 버튼 역할 컴포넌트
function E7ESubmit() {
  const { pending } = useFormStatus();
  return (
    <button disabled={pending}>{pending ? "지둘령" : "경미니? 미누니?"}</button>
  );
}

function App() {
  const [data, setData] = useState({ email: "", password: "" });

  const myAction = async (prevState, formData) => {
    console.log("체킁킁: ", prevState);
    const email = formData.get("email");

    if (!email || email.trim === "") {
      return {
        ...prevState,
        error: {
          email: "email은 필수닝 적어주삼",
        },
      };
    }

    const password = formData.get("password");
    if (!password || password.trim === "") {
      return {
        ...prevState,
        error: {
          password: "password없이 어쩔거얌",
        },
      };
    }

    // 무언가 필요한 일 하겠징, 그게 비동기? 그래서 가짜롱
    await delay(5);

    console.log("출력:", data);

    return {
      error: null,
    };
  };

  const [formState, formAction] = useActionState(myAction, {
    error: null,
  });

  const handleEmail = (e) => {
    setData((prev) => ({ ...prev, email: e.target.value }));
  };

  const handlePassword = (e) => {
    setData((prev) => ({ ...prev, password: e.target.value }));
  };

  return (
    <div
      style={{
        display: "flex",
        justifyContent: "center",
      }}
    >
      <form action={formAction}>
        <div>
          <label htmlFor="email">이메일</label>
          <input
            type="email"
            autoComplete="email"
            name="email"
            value={data.email}
            onChange={handleEmail}
          />
          {formState.error && formState.error.email && (
            <p style={{ color: "red" }}>{formState.error.email}</p>
          )}
        </div>
        <div>
          <label htmlFor="password">아암호</label>
          <input
            type="password"
            autoComplete={"autoComplete"}
            name="password"
            value={data.password}
            onChange={handlePassword}
          />
          {formState.error && formState.error.password && (
            <p style={{ color: "red" }}>{formState.error.password}</p>
          )}
        </div>
        <div style={{ textAlign: "center", marginTop: 5 }}>
          <E7ESubmit />
        </div>
      </form>
    </div>
  );
}


export default App;

 

실행해 보면 똑같이 잘 됨을 알게 해준 회귀본능에 감사한당.

 

혹 맘이 움직였다면 아래 공식 링크가 함께 공부해 줄 친구가 될 것이다.

 

https://react.dev/reference/react/useActionState

https://react.dev/reference/react-dom/hooks/useFormStatus

 

form 관련 제일 많이 사용되는 라이브러링  react-hook-form 추천

https://github.com/react-hook-form/react-hook-form

 


 

흐릿하나 컬러풀 우산들의 큰 흐름이 넉넉하게 저절로 보이는 

대빵 커다란 창문에  조용히 큰 줄기의 비가 계속 내리 내린당.

살짝 한기도 느끼지만, 푸짐한 김이 나는 삼겹살 뒤에 털어넣는

꽁냥꽁냥 깜찍한 소주 한잔이 왜 그렇게 따뜻하다.

비에 젖은 창에는 허세 풍년 조명이 아늑한 저녁을 알린당.

 

앞 자리엔 금수저가 앉아, MZ-단축어 풍년으로 최신 이야기를 푼다.

귀 기울여 거기에 대항할 웃음 유발 복고 스토리를 실타래 엮어본다

이야기 전쟁은 시한폭탄이 준비된 시간에 자폭하듯

빈틈없이 웃음 폭탄을 터트려 시간의 흐름은 어느새 잊혀진당.

꽉꽉 눌러담는 추억이 비명한번 없이 서로를 찾는다.

 

이건 꿈이었고 바램이었당.

실상은 비도 오지 않았고, 창도 고만고만 했고

당근 더불어 지나다니는 우산 1개 없는데다

소주는 꽁냥꽁냥하지도 않았으며, 

삼겹살 아닌 회는 전두엽의 기능을 막아버렸고,

분위기 자물쇠에 갇힌 스토리는 외출 불가에

억지로 밀고 있는 시간은 무겁고 더디기만 했다.

 

현실은 이렇게 낭패였지만  끝은 아니다.

진실은  달콤쌈빡 꿈을 바보처럼  또 꿰메고 있다.

꿈 너 지금 오뎅뎅?  오늘은 별이 별로당.

 

 

https://www.youtube.com/watch?v=3kGAlp_PNUg

 

 

관련글 더보기