[react] input 작성시간 업데이트 하는 방법

subb_ny·2022년 12월 27일
0

⛔️ 문제점 발견

추가할 때마다 작성된 모든 시간들이 다시 업데이트되는 문제점을 발견했다.

const Todo2 = () => {
  const [todos, setTodos] = useState([]); 
  const [todo, setTodo] = useState("");
  const [time, setTime] = useState("");
  // 값을 저장해줄 time선언 
  useEffect(() => {
    setTime(moment().format("YYYY-MM-DD HH:mm:ss"));
  }, [time]);
  const onChange = (e) => {
    setTodo(e.target.value);
  };  //time값이 변화할때마다 업데이트해주기 
  const onSubmit = (e) => {
    e.preventDefault();
    setTime(moment().format("YYYY-MM-DD HH:mm:ss")); //submit 될 때마다 time값 바꿔주기 
    //새로고침 방지
    setTodos((current) => [todo, ...current]);
    //todos 배열에 값 넣기
    setTodo(""); //input안의 값초기화
  };
  return (
    <div>
      <form onSubmit={onSubmit}>
        <input
          onChange={onChange}
          placeholder="할일을 추가하세요"
          value={todo}
</input>
        <button>add to do</button>
      </form>
      {todos.map((todo, num) => {
        return <TodoItem2 todo={todo} key={num} time={time} />;
      })}
    </div>
  );
};
export default Todo2;

<TodoItem2 todo={todo} key={num} time={time} />
의 컴포넌트는 특별한 코드가 없으므로 생략한다 (props로 받아오기만 했다)

👻 위 코드에서 Time 받아오는 방법 (첫번째 시도)

  const [time, setTime] = useState(""); 

useState를 사용하여 time값을 저장해줄 변수를 만든다.

 const onSubmit = (e) => {
    e.preventDefault();
    setTime(moment().format("YYYY-MM-DD HH:mm:ss")); //submit 될 때마다 time값 바꿔주기 
    //새로고침 방지
    setTodos((current) => [todo, ...current]);
    //todos 배열에 값 넣기
    setTodo(""); //input안의 값초기화
  };
  <form onSubmit={onSubmit}>

onSubmit 함수를 만들어서 onSubmit이벤트가 실행될때 moment라이브러리를 사용해서 현재시간을 받아온 후 setTime 함수에 넣어서 time을 바꿔준다.

   {todos.map((todo, num) => {
    return <TodoItem2 todo={todo} key={num} time={time} />;
  })}

TodoItem2 (todo, time을 그려주는 컴포넌트)에 time을 넘겨준다.

😵 위와 같이 코드를 작성하니까 submit이벤트가 실행될 때마다 모든 시간이 계속 업데이트 되었다.

😈 해결방법

시간이 계속 업데이트 되는 이유는 submit할 때마다 setTime함수로 time값을 바꿔주는데 어떠한 저장도 하지 않고 바로 props로 time을 넘기니까 계속해서 업데이트 되는 것이다.
time을 두가지 방법으로 저장해보려고 한다.

1.첫번째 방법 (todos에 객체형태로 넣어주는 방법) => 성공!

  const [todos, setTodos] = useState([]);
  const onSubmit = (e) => {
    e.preventDefault();
    setTime(moment().format("YYYY-MM-DD HH:mm:ss"));
    setTodos((current) => [{ todo: todo, time: time }, ...current]);
    setTodo("");
  };

setTodos((current) => [todo, ...current]);

후 ⭕️

setTodos((current) => [{ todo: todo, time: time }, ...current]);

todos에 같이 넣어주는 방법이다.
todo를 저장하고 보여주기 위해서 todos라는 배열을 만들어서 저장하고 return할때 map함수로 돌려주었다.
time도 저장하기 위해서 todos에 객체형태로 todo와 time을 저장한 후

   {todos.map((item, index) => {
        return <TodoItem key={index} todo={item.todo} time={item.time} />;
      })}

item으로 객체를 받아와서 todo와 time을 객체의 key값으로 props로 넘겨주었다.

2-1번째 방법 - useState를 사용하여 time을 todo와 따로 저장하는 방법 (실패💧 )

  const [time, setTime] = useState("");
  const [alltime, setAlltime] = useState([]);

처음에는 todo와 같이 시간을 저장해줄 배열을 alltime으로 빼서 따로 저장해주는 방법을 생각했다.

 const onSubmit = (event) => {
   event.preventDefault();
   setTime(() => moment().format("YYYY-MM-DD HH:mm:ss"));
   //submit할때 setTime으로 time 바꿔주기 
   setTodos((count) => [todo, ...count]);
   setAlltime((count) => [time, ...count]);
	//time 저장해주기
   setTodo("");
 };

이후에 todos를 map으로 돌리고 time은 alltime의 배열 인덱스값으로 넘겨주었다.
처음에는 todos를 map으로 돌리고 있는 상황에서 alltime도 새로 맵으로 돌려야 하나에 대한 고민을 했었는데
이렇게 돌리고 있는 맵안에 다른 배열의 인덱스값으로 넘겨주는 건 방법이 특이하다고 생각했다. 처음보는 방법이었다.

  {todos.map((item, index) => {
       return (
         <TodoItem
           time={alltime[index]}
           todo={item}
           key={index}
           value={todo}
           index={index}
         />
       );
     })}

그러나 이 코드도 문제점이 있었는데 위와 같이 작성한 시간이
하나씩 밀린다는 것이다. (두번째 빨래하기에 들어간 시간은 그 즉시 작성한 시간이 아니라 첫번째 작성한 시간이 들어간다)

📌 이건 또 왜 밀려?????

alltime을 console에 찍어보니 첫 submit event를 실행할 때에는 초기값인 빈 문자열이 들어왔다. 그 답을 참고 블로그1 참고 블로그2 를 통해서 찾을 수 있었다.

setstate 가 비동기 방식으로 처리 된다는 것이다

그래서

setTime(() => moment().format("YYYY-MM-DD HH:mm:ss"));

setAlltime((count) => [time, ...count]);

이 두 코드가 같은 함수안에서 작동을 하고 있었는데 둘다 비동기 방식으로 동작해서 함수가 실행됐을 때 setTime애서 time을 현재시간으로 바뀐 뒤, alltime배열에 넣는게 아니라 각각 개별적으로 처리 되기 때문에 alltime(배열)에는 time의 초기값인 ''(빈문자열)이 들어가게 되는 것이다.

결국 time을 useState로 관리해줬기 때문이라는 원인을 찾았다!!

2-2번째 방법 let으로 변수할당을 해서 time을 todo와 따로 저장하는 방법(성공🍔)

전코드

setTime(() => moment().format("YYYY-MM-DD HH:mm:ss"));

바꾼 코드

let time = moment().format("YYYY-MM-DD HH:mm:ss");

useState를 사용하지 않고 time을 변수에 저장해주고 onSubmit함수에 넣어줬더니 제대로 작동했다!!!

❗️❗️그렇다면 첫번째 저장 방법 과 두번째 저장 방법 중 어떤 것이 더 효율적일까??

useState의 가장 큰 특징은 상태값이 변경 되었을 때, state가 포함된 컴포넌트를 자동으로 재렌더링 해준다는 것이다. 위 코드에서 time은 submit 이벤트가 일어날때만 변경되는 것이기 때문에 굳이 useState를 쓰지 않아도 된다라는 생각이다.
또한 개인적인 생각은
let을 사용해서 현재시간을 가져오면 moment().format("YYYY-MM-DD HH:mm:ss")가 time에 바로 할당이 되지만, useState는 setTime이라는 setter함수를 거쳐서 넣어줘야 함으로 효율성 측면에서는 일반변수를 사용하는 것이 더 효과적이지 않을까 하는 생각이다.

👩‍💻 여러 삽질을 하면서 느낀점

처음에 이렇게 TodoList에 대해서 깊게 파게 된건 강사님의 코드에 의문점을 가지면서 시작되었다. 여기서 왜 이렇게 쓰셨지 왜 이러면 무한루프가 도는거지? 라는 생각으로 이 코드도 넣어보고 저 코드도 넣어보고 잉??도 많이 하면서 useState나 useEffect에 대한 개념이 좀 더 정리가 된 거 같다는 느낌이 들었다.
위 글에서는 다루지 않았지만 고민하는 와중에 useEffect도 사용해봤는데 오잉 이렇게 된다고?? 라고 생각이 드는 코드가 있어서 그 문제에 대해서도 몇주동안 고민했다.
결국 그에 대한 해답도 찾아서 후속으로 블로그를 쓰려고 한다.

오랜시간 동안 저 간단한 코드로 이것저것 뜯어보고 넣어보면서
시간이 지나면 이해가 될 것을 내가 너무 깊게 들어가고 집착을 하나? 라는 생각이 들기도 했다.
근데 여러 오류들과 마주하고 해결책을 찾는 과정에서 비동기,동기에 대해서도 이해하게 되고, 리액트의 동작원리에 대해 거의 몰랐었는데 이제는 아주 조금은 감이 온 것 같다. ㅎㅎ 내가 어떤 부분이 부족하고 어떤 걸 더 공부하면 좋을 지 알게 된 거 같아서 뿌듯하기도 했다.

내 공부방식에 더 이상 흔들리지 말아야겠다.
맞고 틀린건 없으니까 ㅎㅎ

0개의 댓글