develop

날짜 기능이 있는 게시판 만들기 with datepicker (3) - 응용에서 겪은 문제

crab. 2023. 2. 23. 06:54

추가로 disabled 속성 응용

  • 공식문서에도 적혀 있지만 disabled속성이 있다.
  • 이 속성은 true일때 이 input을 비활성화하는 것인데 이 기능을 이용하면
  • 날짜가 정해지기 전에 시간을 먼저 정하는 것을 방지할 수 있다.
    • 그래야 하는 이유는
    • datepicker는 원래부터가 과거도 선택할 수 있게 되어 있고
    • 나의 경우 기획상 날짜가 당일 날짜일때만 현재시간보다 이전 시간을 선택하지 못하게 해야한다.
    • 이 부분을 datepicker의 자체기능을 찾고 구현하기에는 시간의 소요가 많이 걸릴 것으로 예상되었다.
  • 처음에는 어떻게 해야할까 많은 삽질을 했지만 삼항연산자의 중요성과 활용도를 점점 느끼면서 이번에도 같은 방법으로 해결하게 되었다.
			<DatePicker
                        className="trimError"
                        // placeholderText="HH:MM"
                        selected={startTime}
                        locale={ko}
                        onChange={setStartTime}
                        showPopperArrow={false}
                        timeIntervals={60}
                        dateFormat="HH:mm"
                        showTimeSelect
                        showTimeSelectOnly
                        autoComplete="off"
                        filterTime={possibleTime2}
                        ref={startTimeRef}
                        onBlur={onBlurStartTimeCheck}
                        customInput={<ExampleCustomInput2 />}
                        disabled={!startDate ? true : false}
                      />
  • 여기까지 오기에 많은 경험이 있었지만 결과는 사실 단순하다.
  • disabled 속성에 삼항 연산자로 위에서의 startDate가 true인지 false인지로 disabled속성도 true, false로 바꿔주면 된다.
    • 위의 datepicker에서 날짜가 정해져야 startDate가 값이 변한다.
    const [startDate, setStartDate] = useState(false);
    

날짜 순서가 잘못됐을때의 대처

  • 또한 날짜(시간)의 순서가 잘못될 수도 있다.
  • 이런 경우는 제출 함수에 조건문을 걸어두면 된다.
		if (new Date(open) > new Date(close)) {
      setDateOrderError(true);
      return;
    }
  • 다음 포스팅에서 좀 더 자세히 들어가겠지만 나의 경우 서버에 YYYY-MM-DD HH:mm 형식으로 보내줘야 했기에 startDate와 startTime을 합쳐서 open이라는 상태로 만들어주었다.
const [open, setOpen] = useState("");
//////
useEffect(() => {
    if (startDate !== false && startTime !== false) {
      const year = startDate.getFullYear().toString(); //년도 뒤에 네자리
      const month = ("0" + (startDate.getMonth() + 1)).slice(-2); //월 2자리 (01, 02 ... 12)
      const day = ("0" + startDate.getDate()).slice(-2); //일 2자리 (01, 02 ... 31)
      const hour = ("0" + startTime.getHours()).slice(-2); //시 2자리 (00, 01 ... 23)
      const minute = ("0" + startTime.getMinutes()).slice(-2); //분 2자리 (00, 01 ... 59)
      const second = ("0" + startTime.getSeconds()).slice(-2); //초 2자리 (00, 01 ... 59)
      const returnDate =
        year +
        "-" +
        month +
        "-" +
        day +
        " " +
        hour +
        ":" +
        minute +
        ":" +
        second;
      setOpen(returnDate);
    }
  }, [startDate, startTime, setOpen]);
  • 그래서 open을 다시 date객체로 만들고 close와 단순히 비교를 한다.
    • date객체의 경우 날짜가 빠르면 값이 작다.
    • 그리고 시작날짜가 끝날짜보다 뒤라면 조건문 내부로 들어와 return을 해서 제출함수 자체를 끝내는 걸로 기능을 구현하면 된다.

date형식 변경과 useState의 비동기 문제

  • 이제 이벤트의 날짜를 지정했다면 서버로 그 데이터를 보내주면 된다.
  • 단, 나의 경우 기존의 date객체 형식에서 YYYY-MM-DD HH:mm 으로 보내줘야 할 필요성이 생겼다.
  • 여기서 문제가 생겼는데
    • 만약 유저가 날짜를 지정하고 바로 저장을 누를 때 제출 함수에서 setState를 통해 날짜 형식을 바꾸면 useState의 비동기 문제로 인해 날짜가 변경되지 않은 date객체 형식으로 보내지게 되고
    • 그렇다고 useEffect를 써서 유저가 startDate를 바꾸었을 때 형식을 바꿔주면 바로 바뀐 형식 때문에 input창에 잘못된 값이 나오게 된다.
  • 따라서 내가 해결한 방법은 두가지의 해결방법을 섞은 것으로
    • useEffect를 써서 유저가 날짜를 변경했을때 바로 형식을 바꾸되
    • 새로운 상태값을 사용하는 것이다.
    const [open, setOpen] = useState("");
    //////
    useEffect(() => {
        if (startDate !== false && startTime !== false) {
          const year = startDate.getFullYear().toString(); //년도 뒤에 네자리
          const month = ("0" + (startDate.getMonth() + 1)).slice(-2); //월 2자리 (01, 02 ... 12)
          const day = ("0" + startDate.getDate()).slice(-2); //일 2자리 (01, 02 ... 31)
          const hour = ("0" + startTime.getHours()).slice(-2); //시 2자리 (00, 01 ... 23)
          const minute = ("0" + startTime.getMinutes()).slice(-2); //분 2자리 (00, 01 ... 59)
          const second = ("0" + startTime.getSeconds()).slice(-2); //초 2자리 (00, 01 ... 59)
          const returnDate =
            year +
            "-" +
            month +
            "-" +
            day +
            " " +
            hour +
            ":" +
            minute +
            ":" +
            second;
          setOpen(returnDate);
        }
      }, [startDate, startTime, setOpen]);
    
    • 이렇게 하면 useEffect의 비동기 문제를 해결할 수 있다.
  • useEffect의 비동기문제는 react작업내내 끊임없이 나를 괴롭혀온 문제이다.
  • 계속해서 정리하겠다 생각은 하지만 react의 핵심 기능이나 렌더링과 얽혀있고 알겠다 싶으면 틀리는 경우가 많아 잘 정리를 못하고 있다.
  • 다만 이제는 조금 해결법을 알게 되었는데
    • 하나는 이번처럼 중간에 useEffect를 이용해 setState를 useEffect내부에서 실행시키는 방법이다.
    • 또 다른 하나는 useRef를 이용해서 바꾸는 것이고
    • 마지막은 함수형으로 setState에 넣는 것이다. → 이것은 왜인지 안될때가 꽤 있었다..(다음에 좀 더 깊숙히 이해가 필요하다.)
    • 아무래도 나에게는 useEffect가 논리적으로 이해가 되어서 useEffect를 자주 사용하는 편이다.
      • 다음에는 useRef나 함수형을 확실하게 이해하고 넘어가고 싶다.
  • 아래에 useState가 왜 비동기인지에 대한 좋은 글을 하나 적는 것으로 우선 이 부분은 마무리 하겠다.

useState는 왜 비동기적이지?

  • 왜 useState(정확히는 setState)는 비동기적으로 작동하는 것일까?

페이지를 구성하는데는 수많은 state가 존재한다. 만약 하나하나의 state 변화에 리랜더링을 발생시킨다면 성능 저하가 발생할 것이다.이를 해결하기 위해서, React는 setState가 연속 호출되면 배치(batch) 처리를 통해 한번에 랜더링하게 하는 것이다.많은 setState를 연속으로 사용해도 배치 처리로 인해 랜더링은 한번만 되는 것이다.

배치(batch)란 React가 여러개의 state 업데이트를 하나의 리랜더링으로 묶는 것을 의미한다.React는 16ms 동안 변경된 상태 값들을 하나로 묶는다. (16ms 단위로 배치를 진행한다.)

  • 이러한 이유로 useState가 비동기 처리 되는 것이다.
  • 하나를 바꾸면 바로 하나가 바뀐 상태가 업데이트되는 방식이 아니었던 것이다.