<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>taejun.log</title>
        <link>https://velog.io/</link>
        <description>𝙸 𝚊𝚖 𝚊 𝚌𝚞𝚛𝚒𝚘𝚞𝚜 𝚍𝚎𝚟𝚎𝚕𝚘𝚙𝚎𝚛 𝚠𝚑𝚘 𝚎𝚗𝚓𝚘𝚢𝚜 𝚍𝚎𝚏𝚒𝚗𝚒𝚗𝚐 𝚊 𝚙𝚛𝚘𝚋𝚕𝚎𝚖. 🇰🇷👩🏻‍💻</description>
        <lastBuildDate>Sat, 02 Sep 2023 08:25:12 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>taejun.log</title>
            <url>https://velog.velcdn.com/images/taejun-baek/profile/1b519811-1674-4019-8885-aef08039465d/image.JPG</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. taejun.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/taejun-baek" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[리덕스에서 리코일로 ]]></title>
            <link>https://velog.io/@taejun-baek/%EB%A6%AC%EB%8D%95%EC%8A%A4%EC%97%90%EC%84%9C-%EB%A6%AC%EC%BD%94%EC%9D%BC%EB%A1%9C</link>
            <guid>https://velog.io/@taejun-baek/%EB%A6%AC%EB%8D%95%EC%8A%A4%EC%97%90%EC%84%9C-%EB%A6%AC%EC%BD%94%EC%9D%BC%EB%A1%9C</guid>
            <pubDate>Sat, 02 Sep 2023 08:25:12 GMT</pubDate>
            <description><![CDATA[<p>프로젝트에서 기존에 상태관리를 위해 써왔던것은 리덕스 입니다.
그러나 저희 프로젝트에는 아직 대규모로 진행 하지 않았고 
불필요한 보일러 플레이트가 많은 리덕스보다는 리코일을 사용이 더 적합해서
사실 내가 너무 써보고 싶었음</p>
<h4 id="기존에-사용했던-로그인-관련-상태-관리-리덕스">기존에 사용했던 로그인 관련 상태 관리 리덕스</h4>
<pre><code class="language-jsx">import { createSlice } from &quot;@reduxjs/toolkit&quot;;
import { getCookie } from &quot;../../utils/cookieUtils&quot;;

  function deleteCookie(name: string) {
    document.cookie = name + &quot;=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;&quot;;
  }

const accessToken = getCookie(&quot;access_token&quot;);


const initialState = {
  isLogin: !!accessToken,
};

const isLoginSlice = createSlice({
  name: &quot;isLogin&quot;,
  initialState,
  reducers: {
    logIn: (state) =&gt; {
        state.isLogin = true;
    },
    logOff: (state) =&gt; {
        state.isLogin = false;
        deleteCookie(&quot;access_token&quot;); // 엑세스 토큰 삭제
        deleteCookie(&quot;refresh_token&quot;); // 리프레쉬 토큰 삭제
        deleteCookie(&quot;nickname&quot;); // 닉네임 삭제
        deleteCookie(&quot;emoticon&quot;); // 이모티콘 삭제
        deleteCookie(&quot;email&quot;); // 이메일 삭제

    },
  },
});

export const { logIn, logOff } = isLoginSlice.actions;
export default isLoginSlice.reducer;</code></pre>
<h4 id="그리고-아톰으로-저장하여-사용한-리코일-코드">그리고 아톰으로 저장하여 사용한 리코일 코드</h4>
<pre><code class="language-jsx">import { atom, useRecoilState } from &#39;recoil&#39;;
import { getCookie } from &#39;../utils/cookieUtils&#39;;


// 리덕스에서 사용했던 deleteCookie 함수를 그대로 사용합니다.
function deleteCookie(name: string) {
  document.cookie = name + &#39;=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;&#39;;
}

// access_token 쿠키를 가져옵니다.
const accessToken = getCookie(&#39;access_token&#39;);

// Recoil 아톰(atom)을 사용하여 상태를 정의합니다.
export const isLoggedInState = atom({
  key: &#39;isLoggedInState&#39;,
  default: !!accessToken, // 기본값은 access_token 쿠키가 있을 경우 true, 없을 경우 false
});

// 리듀서 대신 Recoil 상태를 사용합니다.
export function useLoginStatus() {
  const [isLoggedIn, setIsLoggedIn] = useRecoilState(isLoggedInState);

  const logIn = () =&gt; {
    setIsLoggedIn(true);
  };

  const logOff = () =&gt; {
    setIsLoggedIn(false);
    // 쿠키 삭제 등의 작업을 수행합니다.
    deleteCookie(&#39;access_token&#39;);
    deleteCookie(&#39;refresh_token&#39;);
    deleteCookie(&#39;nickname&#39;);
    deleteCookie(&#39;emoticon&#39;);
    deleteCookie(&#39;email&#39;);
  };

  return { isLoggedIn, logIn, logOff };
}</code></pre>
<p>이제 로그인 페이지에서</p>
<pre><code class="language-jsx">  const [isLoggedIn, setIsLoggedIn] = useRecoilState(isLoggedInState);</code></pre>
<p>로그인을 상태를 useRecoilState를 사용해 업데이트 해주면 아톰에서 그걸 저장하고?</p>
<p>내가 사용하고 싶은 컨퍼넌트에서 useRecoilValue를 통해 사용 해주면 된다.</p>
<pre><code class="language-jsx">  const isLoggedIn = useRecoilValue(isLoggedInState);</code></pre>
<h2 id="결과">결과</h2>
<p>너무나 편함!!!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[리액트 SSE 연결]]></title>
            <link>https://velog.io/@taejun-baek/%EB%A6%AC%EC%95%A1%ED%8A%B8-SSE-%EC%97%B0%EA%B2%B0</link>
            <guid>https://velog.io/@taejun-baek/%EB%A6%AC%EC%95%A1%ED%8A%B8-SSE-%EC%97%B0%EA%B2%B0</guid>
            <pubDate>Thu, 31 Aug 2023 08:28:48 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/taejun-baek/post/206e8fed-944a-4d05-b191-cd8965797778/image.png" alt="">
오늘은 SSE 연결을 통해 서버에서 댓글이나 메세지를 받았을때 바로 알림을 받을 수 있는 기능을 
구현 했다.
백엔드쪽 자료는 많았지만 프론트엔드쪽은 자료가 적어서 너무나 힘들었기에 
많은 사람들에게 도움이 되면 좋겠지만 나의 글솜씨는 좋지 않기 때문에 너무 기대를 안했으면 좋겠다.</p>
<p>아래는 프론트엔드의 기본 적인 코드 나는 토큰을 쿠키에 담아서 저장했고 이걸 헤더에 담아 주었다.
아참 시작전에 EventSourcePolyfill을 설치하고 import 해주자</p>
<pre><code class="language-jsx">useEffect(() =&gt; {
    const accessToken = getCookie(&quot;access_token&quot;);
    const refreshToken = getCookie(&quot;refresh_token&quot;);

    const eventSourceInitDict: any = {
      headers: {
        accessToken: accessToken || &quot;&quot;,
        refreshToken: refreshToken || &quot;&quot;,
      },
    };

    sse.current = new EventSourcePolyfill(
      `${process.env.REACT_APP_SERVER_URL}/서버에서 준 주소,
      eventSourceInitDict
    );

    sse.current.onopen = (e) =&gt; {
      setIsStarted(true);
      console.log(&quot;[sse] 연결이 열렸습니다&quot;, { e });
    };

    sse.current.addEventListener(&quot;addComment&quot;, (event: any) =&gt; {
      const eventData = JSON.parse(event.data);
      console.log(&quot;댓글을 받았습니다:&quot;, eventData);
      setEventDataList((eventDataList) =&gt; [...eventDataList, eventData]);
    });

    sse.current.addEventListener(&quot;addMessageRoom&quot;, (event: any) =&gt; {
      const eventData = JSON.parse(event.data);
      console.log(&quot;메세지를 받았습니다:&quot;, eventData);
      setEventDataList((eventDataList) =&gt; [...eventDataList, eventData]);
    });


    sse.current.onerror = (err) =&gt; {
      console.log(&quot;[sse] 에러 발생&quot;, { err });
    };

    // 컴포넌트가 언마운트될 때 SSE 연결을 해제합니다.
    return () =&gt; {
         if (sse.current) {
        sse.current.close();
      }
    };
  }, []);</code></pre>
<h3 id="문제점">문제점</h3>
<p>SSE가 받아 지는것을 성공했지만 위의 이미지처럼 나는 이걸 새로고침을 해도 저장할수 있어야
했지만 SSE는 그것이 불가능했다.</p>
<h3 id="해결책">해결책</h3>
<p>HTTP방식으로 서버단에서 따로 저장한것을 GET도 하는 방식을 추가 대신 
우리가 지속적으로 요청을 하면 SSE를 사용하는 의미가 없으니 </p>
<pre><code class="language-jsx">useEffect(() =&gt; {
  if (AlertData) {
    setEventDataList(AlertData);
  }
}, [AlertData]);</code></pre>
<p>useEffect로 Get 데이터가 있으면 SSE데이터에 추가하는 방식이다.</p>
<h3 id="문제점2">문제점2</h3>
<p>SSE로 받은 데이터를 삭제를 시도하면 HTTP방식으로는ㄴ 아직 데이터가 없는 상태이다.
즉 새로고침을 해줘야 GET으로 받은 데이터가 존재함이 되므로 삭제가 가능 하게 된다.</p>
<h3 id="해결책2">해결책2</h3>
<p>useEffect로 SSE로 데이터를 받으면 queryClient 시켜서 새로고침을 시켜준다.</p>
<pre><code class="language-jsx">useEffect(() =&gt; {
  if (eventDataList) {
    queryClient.invalidateQueries(&quot;getAlert&quot;);
  }
}, [eventDataList]);</code></pre>
<h3 id="남은숙제">남은숙제</h3>
<p>클릭하면 해당 메세지창이나 해당 댓글 페이지로 이동하기
메세지는 잘되는데 
해당 댓글에는 두개의 쿼리스트링을 받는데 id값이 해당 게시물의 id가 아니라
댓글의 id로 따라가서 엉뚱한 게시물로 이동이 됨...</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[웹소켓 과 SSE(Server-Sent-Event) 차이점 알아보고 사용해보기]]></title>
            <link>https://velog.io/@taejun-baek/%EC%9B%B9%EC%86%8C%EC%BC%93-%EA%B3%BC-SSEServer-Sent-Event-%EC%B0%A8%EC%9D%B4%EC%A0%90-%EC%95%8C%EC%95%84%EB%B3%B4%EA%B3%A0-%EC%82%AC%EC%9A%A9%ED%95%B4%EB%B3%B4%EA%B8%B0</link>
            <guid>https://velog.io/@taejun-baek/%EC%9B%B9%EC%86%8C%EC%BC%93-%EA%B3%BC-SSEServer-Sent-Event-%EC%B0%A8%EC%9D%B4%EC%A0%90-%EC%95%8C%EC%95%84%EB%B3%B4%EA%B3%A0-%EC%82%AC%EC%9A%A9%ED%95%B4%EB%B3%B4%EA%B8%B0</guid>
            <pubDate>Thu, 24 Aug 2023 05:31:53 GMT</pubDate>
            <description><![CDATA[<p>최근에 어떤 이벤트가 생겼을 때 client side에 ui를 업데이트해야 되는 기능을 구현해야 됐었습니다. 처음에는 이런 경우에 사용할 수 있는 것이 socket 밖에 몰라서  socket.io를 사용해서 socket으로 만들다가 웹소켓을 공부하다가 보니 SSE(Server-Sent-Event)라는 것을 알게 되었습니다. 제가 평소에 공부를 해두었다면 웹소켓으로 안 만들고 SSE를 사용해서 만들었을 텐데 시간 낭비를 해버렸습니다. 이래서 평소에 공부를 해야 되는 것 같습니다. Socket과 SSE에 가장 큰 차이점을 하나 말해보라고 한다면 Socket은 양방향으로 데이터를 주고받을 수 있지만 SSE(Server-Sent-Event)를 사용하게 되면 클라이언트는 데이터를 받을 수만 있게 됩니다. 그러니까 어떤 기능이 필요한지에 따라서 뭐를 사용할지 결정하면 됩니다. </p>
<p>웹소켓과 SSE(Server-Sent-Event)에 차이점</p>
<p>Socket과 SSE에 가장 큰 차이점을 하나 말해보라고 한다면 Socket은 양방향(bidirectional)으로 데이터를 주고 받을 수 있지만 SSE(Server-Sent-Event)를 사용하게 되면 클라이언트는 데이터를 받을 수만(mono-directional) 있게 됩니다. 그러니까 어떤 기능이 필요한지에 따라서 뭐를 사용할지 결정하면 됩니다. </p>
<p> <img src="https://velog.velcdn.com/images/taejun-baek/post/e8dd25a3-1440-4bcc-97e5-1e9e5c2f764a/image.png" alt="">
마지막으로 총 청리</p>
<p>웹소켓과 SSE에 가장 큰 차이점은 통신 방향이다. 웹소켓은 양방향 SSE는 단방향이기 때문에 클라이언트가 데이터를 보낼 필요가 있을 때는 소켓. 예를 들어 FPS 게임 같은 경우. 하지만 SSE는 서버에서 클라이언트로만 데이터 전송이 가능하기 때문에 주로 노티를 줄 때 많이 사용된다. 지원하는 브라우저 같은 경우 IE를 빼고 생각한다면 둘 다 거의 대부분에 모던 브라우저에서 Polyfills 없이 사용이 가능하다. 하지만  Server-Sent Event도 Polyfills를 사용하면 되기 때문에 큰 문제가 되진 않는다. SSE를 사용할 때 buffer만 꺼주면 소켓이랑 같이 실시간 업데이트가 가능하다. SSE를 사용할 때 주의할 점은 HTTP/1을 사용할 경우 브라우저에 최대 연결 수가 보통 6개로 제한되기 때문에 6개 이상 텝을 열었을 때 작동이 잘 안 될 수가 있다. 하지만 HTTP/2를 사용할 경우 기본 최대 연결 수가 100이 됨으로 문제가 될 가능성이 많이 줄어든다. 소켓은 서버와 연결이 끈겼을 때 자동으로 연결해주는 기능이 없기 때문에 스스로 코드를 짜야되지만 서버사이드 이벤트 같은 경우에는 자동으로 3초마다 한 번씩 확인을 한다. 서버사이드 이벤트 같은 경우에는 firewall이 있는 경우도 사용이 큰 문제없이 가능하기 때문에 firewall이 있을 경우 사용을 고려하는 것도 좋다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[input은 줄넘김이 안된다]]></title>
            <link>https://velog.io/@taejun-baek/input%EC%9D%80-%EC%A4%84%EB%84%98%EA%B9%80%EC%9D%B4-%EC%95%88%EB%90%9C%EB%8B%A4</link>
            <guid>https://velog.io/@taejun-baek/input%EC%9D%80-%EC%A4%84%EB%84%98%EA%B9%80%EC%9D%B4-%EC%95%88%EB%90%9C%EB%8B%A4</guid>
            <pubDate>Mon, 21 Aug 2023 06:20:59 GMT</pubDate>
            <description><![CDATA[<p>포스트 페이지를 리펙토링 과정중에
신경안쓴 부분이 있는데 내용을 기입할때 줄바꿈이 안되는것이었다....
인풋태그가 줄바꿈이 안된다는걸 처음 알았음...</p>
<p>그래서 textarea로 바꾸어 주었다 </p>
<pre><code class="language-jsx">const ContentInput = styled.textarea`
  width: 1200px;
  height: 180px;
  font-size: 16px;
  margin: 40px auto 60px;
  padding-left: 20px;
  padding-top: 20px;
  border-radius: 8px;
  border: solid 1px #cfced7;
  background-color: #fff;
  outline: none;
  color: #484848;
  &amp;::placeholder { 
    color:  #dddce3;;
  }
`</code></pre>
<p>근데 왜 플레이홀더 색깔이 안바뀌냐??
그건 </p>
<pre><code class="language-jsx"> &amp;::placeholder { 
    color:  #dddce3;;
  }</code></pre>
<p>이렇게 넣어줘야 바뀜 이것땜에 1시간동안 고생함 ㅠㅠ</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[네비게이션바 만들기 feat.에어비앤비]]></title>
            <link>https://velog.io/@taejun-baek/%EB%84%A4%EB%B9%84%EA%B2%8C%EC%9D%B4%EC%85%98%EB%B0%94-%EB%A7%8C%EB%93%A4%EA%B8%B0-feat.%EC%97%90%EC%96%B4%EB%B9%84%EC%95%A4%EB%B9%84</link>
            <guid>https://velog.io/@taejun-baek/%EB%84%A4%EB%B9%84%EA%B2%8C%EC%9D%B4%EC%85%98%EB%B0%94-%EB%A7%8C%EB%93%A4%EA%B8%B0-feat.%EC%97%90%EC%96%B4%EB%B9%84%EC%95%A4%EB%B9%84</guid>
            <pubDate>Tue, 15 Aug 2023 01:59:23 GMT</pubDate>
            <description><![CDATA[<p>프로젝트를 진행중 디자이너님에게 생전 처음 보는 내비게이션바를 만들어 달라는 미션을 받았다
딱보니 쉽지 안겠구나라는걸 느끼고 라이브러리가 있나 찾아 봤지만 영 원하는 ui를 찾기 못했고
레퍼런스가 필요했는데 에어비앤비에 비슷한게 있었다.<img src="https://velog.velcdn.com/images/taejun-baek/post/f91956e3-93d5-49d8-8739-96567763feec/image.png" alt=""></p>
<h2 id="결과물">결과물</h2>
<p><img src="https://velog.velcdn.com/images/taejun-baek/post/f1c42668-25f1-4223-ad78-7c3eb5c9f107/image.png" alt="">
저렇게 레이아웃 되면서 클릭하면 선택할 수 있는 모달이 내려 온다.</p>
<p>처음에는 어렵게 생각했지만 div박스안에 div박스를 만들어 클릭 했을때만 저 알약 같은 박스만 
레이아웃 되게 하면 되는거 였음</p>
<pre><code class="language-jsx"> const handleBoxClick = (index: number) =&gt; {
        setSelectedBox(index);
        setIsWhele(false);
        setIsDate(false);
        setIsTime(false);

        if (index === 0) {
          setIsWhele(true);
        } else if (index === 1) {
          setIsDate(true);
        } else if (index === 2) {
          setIsTime(true);
        }
      };</code></pre>
<p>총 3개의 박스가 있으니 조건을 걸어 둔다.</p>
<pre><code class="language-jsx">    &lt;NavigationBoxRayout&gt;
            &lt;NavRayout&gt;
            &lt;InnerBox
            onClick={() =&gt; {handleBoxClick(0); setIsWhele(true);}} highlighted={selectedBox === 0}&gt;
                    &lt;TextBox&gt;
                        &lt;TextContent&gt;여행 카테고리&lt;/TextContent&gt;
                        &lt;TextContent2&gt;{selectedCountry ? selectedCountry : &quot;여행지를 선택해주세요.&quot;}&lt;/TextContent2&gt;
                    &lt;/TextBox&gt;
                &lt;/InnerBox&gt;

                &lt;InnerBox
                onClick={() =&gt; {handleBoxClick(1); setIsDate(true);}} highlighted={selectedBox === 1}&gt;
                &lt;TextBox&gt;
                        &lt;TextContent&gt;날짜&lt;/TextContent&gt;
                        &lt;TextContent2&gt;{formattedDate ? formattedDate : &quot;날짜를 선택해주세요.&quot;}&lt;/TextContent2&gt;
                    &lt;/TextBox&gt;
                &lt;/InnerBox&gt;
                &lt;InnerBox onClick={() =&gt;{handleBoxClick(2); setIsTime(true);}} highlighted={selectedBox === 2}&gt;
                &lt;TextBox&gt;
                        &lt;TextContent&gt;시간&lt;/TextContent&gt;
                        &lt;TextContent2&gt;시간을 선택해주세요.&lt;/TextContent2&gt;
                    &lt;/TextBox&gt;
                &lt;/InnerBox&gt;
            &lt;/NavRayout&gt;
        &lt;/NavigationBoxRayout&gt;


        {/* 모달 부분 */}
        &lt;ModalRayout&gt;
            {isWhele &amp;&amp; (
            &lt;SelectCountry id={param} onClick={setSelectedCountry} /&gt;
            )}
            {isDate &amp;&amp; (
           &lt;CustomCalendar setFormattedDate={setFormattedDate} /&gt; 
            )}
            {isTime &amp;&amp; (
             &lt;Clock value={selectedTime} onChange={setSelectedTime} /&gt;
            )}
        &lt;/ModalRayout&gt;</code></pre>
<p>이러게 온클릭이벤트를 걸어주고 프롭스를 내려준다.</p>
<pre><code class="language-jsx">const InnerBox = styled.div&lt;InnerBoxProps&gt;`
    width: 331px;
    height: 84px;
    display: flex;
    padding: 20.4px 140px 20.4px 38px;
  ${({ highlighted }) =&gt; highlighted &amp;&amp; `
    width: 331px;
    height: 84px;
    display: flex;
    padding: 20.4px 140px 20.4px 38px;
    border-radius: 58.7px;
    box-shadow: 1.3px 0 16.6px 0 rgba(0, 0, 0, 0.25);
    border: solid 1px #2bde97;
    background-color: #fff;
  `}
`;</code></pre>
<p>마무리는 이렇게 효과전과 효과후를 만들어 주면 끝!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[메인 페이지가 데이터를 받지 못할때]]></title>
            <link>https://velog.io/@taejun-baek/%EB%A9%94%EC%9D%B8-%ED%8E%98%EC%9D%B4%EC%A7%80-%EB%A6%AC%ED%8E%99%ED%86%A0%EB%A7%81</link>
            <guid>https://velog.io/@taejun-baek/%EB%A9%94%EC%9D%B8-%ED%8E%98%EC%9D%B4%EC%A7%80-%EB%A6%AC%ED%8E%99%ED%86%A0%EB%A7%81</guid>
            <pubDate>Tue, 08 Aug 2023 12:41:23 GMT</pubDate>
            <description><![CDATA[<p>갑자기 발견한 오류
처음 메인 페이지를 랜더링 하면 서버에서 데이터를 잘가져오지만
다른 페이지로 갔다가 뒤로가기로 돌아가면 이상하게 로딩이 되지 않는것
그래서 서버에서 데이터를 가져올 시간이 없어 리액트는 데이터가 없어 에러를 뛰어 버렸다.</p>
<h3 id="첫시도">첫시도</h3>
<p>데이터를 맵으로 뿌리는곳에 옵셔널체인징을 걸어 보았다 </p>
<h4 id="결과-실패">결과 실패</h4>
<h3 id="두번째-시도">두번째 시도</h3>
<p>유즈이펙트를 사용하면 되지 않을까라는 생각에 사용해봄
하지만 여전히 로딩중이 띄어지지 않았다..</p>
<h4 id="결과-실패-1">결과 실패</h4>
<h3 id="세번째-시도">세번째 시도</h3>
<p>다량의 데이터를 가져오는데 시간이 필요하다면 셋타임 아웃을 걸어 데이터를
받을 시간을 벌어보자</p>
<h4 id="결과-성공">결과 성공</h4>
<p>아래는 코드</p>
<pre><code class="language-jsx">export const Main: React.FC = () =&gt; {
  const queryClient = useQueryClient();
  const [showData, setShowData] = useState(false);
  const { isLoading, isError, data } = useQuery(&quot;mainPost&quot;, getHomePosts);

  useEffect(() =&gt; {
    const delay = 300; 
    if (!isLoading &amp;&amp; !isError) {
      setTimeout(() =&gt; {
        setShowData(true);
      }, delay);
    }
  }, [isLoading, isError]);

  if (isError) {
    return &lt;p&gt;오류가 발생하였습니다...!&lt;/p&gt;;
  }

  console.log(&quot;data&quot;, data)

  return (
    &lt;div&gt;
      &lt;Header/&gt;
      &lt;StCardContainer&gt;
      {showData &amp;&amp; data?.map((item: cardItem) =&gt; (
          &lt;Cards key={item.id} items={item} /&gt;
        ))}
      &lt;/StCardContainer&gt;
    &lt;/div&gt;
  )
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[카카오톡 소셜 로그인 {리액트}]]></title>
            <link>https://velog.io/@taejun-baek/%EC%B9%B4%EC%B9%B4%EC%98%A4%ED%86%A1-%EC%86%8C%EC%85%9C-%EB%A1%9C%EA%B7%B8%EC%9D%B8-%EB%A6%AC%EC%95%A1%ED%8A%B8</link>
            <guid>https://velog.io/@taejun-baek/%EC%B9%B4%EC%B9%B4%EC%98%A4%ED%86%A1-%EC%86%8C%EC%85%9C-%EB%A1%9C%EA%B7%B8%EC%9D%B8-%EB%A6%AC%EC%95%A1%ED%8A%B8</guid>
            <pubDate>Sun, 06 Aug 2023 11:46:13 GMT</pubDate>
            <description><![CDATA[<h3 id="과정">과정</h3>
<p>1.버튼 눌러 로그인화면 띄우기
2.카카오에게 인가 코드 받아오기
3.인가코드는 주소의 쿼리스트링에 담아져서 제공된다. 이걸 파싱해서 백엔드에게 전달한다.
4.(백엔드는 우리가 준 코드를 알아서 처리해서 토큰을 준다.)
5.그 토큰을 받아서 로그인을 유지한다.</p>
<h1 id="1-버튼-눌러-로그인화면-띄우기">1. 버튼 눌러 로그인화면 띄우기</h1>
<p>버튼을 누르면 카카오에서 제공하는 로그인 화면을 띄워주기만 하면 된다.
카카오에서 제공하는 로그인 화면은 아래 링크
<code>https://kauth.kakao.com/oauth/authorize?client_id=${REST_API_KEY}&amp;redirect_uri=${REDIRECT_URI}&amp;response_type=code</code></p>
<p>안에 있는 변수인 REST_API_KEY, REDIRECT_URI는 백엔드가 kakao developers에서 얻어와서 줄 것 이다. 저 두 변수는 유출되면 안되니 조심하자.</p>
<p>그리고 window.location.href를 이용해 주소를 바꿨다.</p>
<pre><code class="language-jsx">// ----------------------------------------카카오 로그인
  const REST_API_KEY = &#39;백엔드에서 받을거&#39;;
  const REDIRECT_URI = &#39;백엔드에서 받을거&#39;;
  const link = `https://kauth.kakao.com/oauth/authorize?client_id=${REST_API_KEY}&amp;redirect_uri=${REDIRECT_URI}&amp;response_type=code`;

  const kakaoLoginHandler = () =&gt; {
    window.location.href = link;
  };

return (
     &lt;Button
          color={&#39;kakaoLogin&#39;} 
          onClick={kakaoLoginHandler}
          size={&#39;large&#39;}
          name={&quot;Kakao로 시작하기&quot;}
          kakao={true}
          /&gt;
    );
};

</code></pre>
<h1 id="2-카카오에게-인가-코드-받아오기">2. 카카오에게 인가 코드 받아오기</h1>
<p>인가 코드는 어떻게 받아올까?</p>
<p>1번을 잘 수행했다면 페이지에 로그인 하기 버튼이 생겼을 것이다. 이걸 누르면 카카오 계정 정보를 입력할 수 있는 창이 뜬다.</p>
<p>그리고 제대로 했다면 크롬 주소를 한번 봐보자.</p>
<p>백엔드에서 받은 REDIRECT_URI에 ?code?=이상한코드가한가득 이런게 똥꼬에 붙어있을 것이다.
이게 카카오에서 주는 인가 코드이다. 이걸 백엔드한테 주기만 하면 된다!</p>
<h1 id="3-인가코드는-주소의-쿼리스트링에-담아져서-제공된다-이걸-파싱해서-백엔드에게-전달한다">3. 인가코드는 주소의 쿼리스트링에 담아져서 제공된다. 이걸 파싱해서 백엔드에게 전달한다.</h1>
<p>파싱해서 백엔드에게 전달하기 위해선 파싱하는 코드도 필요하다. 그걸 어디다 적을 것이냐..</p>
<p>나는 따로 Recirection이라는 페이지를 팠다. 백엔드에서 준 URI는<a href="http://localhost:3000/kakao/callback%EC%9D%B4%EB%AF%80%EB%A1%9C">http://localhost:3000/kakao/callback이므로</a> /kakao/callback에 받은 코드를 백엔드에 전달할 로직이 작성된 Redirection 페이지를 띄울 것이다.</p>
<p>아래처럼 작성해 연결해주었다.</p>
<pre><code class="language-jsx">const code = window.location.search; // code?=이상한코드가한가득</code></pre>
<p>나는 저대로 달라해서 줬지만 백엔드에서 인가 코드 자체만 달라고 한다면 아래처럼 받아오자.</p>
<pre><code class="language-jsx">const code = new URL(dococument.location.toString()).searchParams.get(&#39;code&#39;); // 이상한코드가한가득</code></pre>
<p>이제 code안에 담아진 카카오의 인가 코드를 백엔드로 보내주기만 하면 된다.</p>
<pre><code class="language-jsx">useEffect(() =&gt; {

  getKakaoToken(code)
  alert(&quot;로그인을 완료하였습니다.&quot;)

  navigate(&#39;/&#39;)


}, []);
  return (
    &lt;div&gt;Redirection&lt;/div&gt; 
  )
}</code></pre>
<p>나는 리액트쿼리를 사용했다 아래는 api요청</p>
<pre><code class="language-jsx">// 카카오 토큰 받아오기
  const getKakaoToken = async (code: string | null) =&gt; {
    try {
      const response = await instance.get(`백엔드: 여기로 코드주세요~ 하는 주소를 코드?code=${code}`)

    document.cookie = `accessToken=${response.headers.accesstoken}; path=/;`;
    document.cookie = `refreshToken=${response.headers.refreshtoken}; path=/`;

    return response.data;
    } catch (error) {
      console.error(error);
    }
  }</code></pre>
<h1 id="5-그-토큰을-받아서-로그인을-유지한다">5. 그 토큰을 받아서 로그인을 유지한다.</h1>
<p>위에 코드에서 보듯 나는 쿠키에 저장한 토큰을 사용했다</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Redirection]]></title>
            <link>https://velog.io/@taejun-baek/Redirection</link>
            <guid>https://velog.io/@taejun-baek/Redirection</guid>
            <pubDate>Wed, 02 Aug 2023 03:04:25 GMT</pubDate>
            <description><![CDATA[<pre><code class="language-jsx">import { useEffect } from &quot;react&quot;;
import { useNavigate } from &quot;react-router-dom&quot;;
import axios from &quot;axios&quot;;

const Redirection = () =&gt; {
const code = new URL(window.location.toString()).searchParams.get(&quot;code&quot;);
const navigate = useNavigate();
useEffect(() =&gt; {
axios.post(`${process.env.REACT_APP_SERVER}/api/user/kakao/login?code=${code}`).then((r) =&gt; {
document.cookie = `accessToken=${r.headers.authorization}; path=/;`;
navigate(&quot;/&quot;);
window.location.reload();
});
});

return &lt;div&gt;로그인 중입니다.&lt;/div&gt;;
};

export default Redirection;</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[useTransition]]></title>
            <link>https://velog.io/@taejun-baek/useTransition</link>
            <guid>https://velog.io/@taejun-baek/useTransition</guid>
            <pubDate>Tue, 01 Aug 2023 01:34:19 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/taejun-baek/post/5bf3bbc6-47c5-4fa9-9d12-feacce238ee3/image.png" alt=""></p>
<p>비동기로 뭔가를 할때 지연이 있을때 그 지연을 직접 세팅해서 넣어줄수 있는 새로운 훅</p>
<p>처음 들었을때 이걸 스피너의 로딩 화면을 일부러 0.5초 정도 보여주면 어떨까라는 생각이 들어서
저장 해둠
나중에 프로젝트에서 로딩 스피너를 사용할때 로딩화면을 0.5초정도 더 보여줘서
로딩 다음 화면에서 이미지를 완전히 받아올때까지 기다려주는 역활을 하지 않을까?</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[오늘의집 클론 코딩 결과물]]></title>
            <link>https://velog.io/@taejun-baek/%EC%98%A4%EB%8A%98%EC%9D%98%EC%A7%91-%ED%81%B4%EB%A1%A0-%EC%BD%94%EB%94%A9-%EA%B2%B0%EA%B3%BC%EB%AC%BC</link>
            <guid>https://velog.io/@taejun-baek/%EC%98%A4%EB%8A%98%EC%9D%98%EC%A7%91-%ED%81%B4%EB%A1%A0-%EC%BD%94%EB%94%A9-%EA%B2%B0%EA%B3%BC%EB%AC%BC</guid>
            <pubDate>Sun, 30 Jul 2023 13:45:23 GMT</pubDate>
            <description><![CDATA[<p>오늘의집 클론 코딩의 결과물이 완성되었습니다.
실제 작업시간이 5일정도라서 밤을 새는 경우도 많았습니다.
그중에 좋아요와 스크랩을 구현하는데 상당히 까다로웠습니다.
좋아요와 스크랩 갯수는 잘전달되는데 좋아요가 되면 파란색으로 유지해야 하는데
새로고침을 하면 다시 원래색으로 돌아가는 문제가 이었습니다.
리덕스로 시도해보았지만 실패 나중에서야 로컬 스코리지에 저장해야 한다는걸 깨달았습니다.
아래는 로컬 스토리지에 저장하여 가져오는 코드
<img src="https://velog.velcdn.com/images/taejun-baek/post/7459234c-fc13-480c-b526-01b798d09fa0/image.png" alt=""></p>
<p>그리고 힘들었던점은 CSS.. 하나만 틀려도 이쪽 저쪽 난리가 아니었던...
그래도 결과물을 보니 뿌듯합니다.</p>
<h1 id="아래는-결과물">아래는 결과물</h1>
<h3 id="메인화면">메인화면</h3>
<p><img src="https://velog.velcdn.com/images/taejun-baek/post/419578a1-6867-4633-94b0-ab8e00ec07e2/image.png" alt=""></p>
<h3 id="로그인-화면">로그인 화면</h3>
<p><img src="https://velog.velcdn.com/images/taejun-baek/post/8e3a93c4-2c2e-4f31-8c71-e895d9665e4f/image.png" alt=""></p>
<h3 id="글쓰기-화면">글쓰기 화면</h3>
<p><img src="https://velog.velcdn.com/images/taejun-baek/post/5d42c2e8-ad15-4164-b400-f89c6ba9e7d2/image.png" alt=""></p>
<h3 id="상세페이지">상세페이지</h3>
<p><img src="https://velog.velcdn.com/images/taejun-baek/post/84711b74-a097-45dc-af05-5da1699d2c54/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[좋아요 기능 비동기를 리액트 쿼리로]]></title>
            <link>https://velog.io/@taejun-baek/%EC%A2%8B%EC%95%84%EC%9A%94-%EA%B8%B0%EB%8A%A5-%EB%B9%84%EB%8F%99%EA%B8%B0%EB%A5%BC-%EB%A6%AC%EC%95%A1%ED%8A%B8-%EC%BF%BC%EB%A6%AC%EB%A1%9C</link>
            <guid>https://velog.io/@taejun-baek/%EC%A2%8B%EC%95%84%EC%9A%94-%EA%B8%B0%EB%8A%A5-%EB%B9%84%EB%8F%99%EA%B8%B0%EB%A5%BC-%EB%A6%AC%EC%95%A1%ED%8A%B8-%EC%BF%BC%EB%A6%AC%EB%A1%9C</guid>
            <pubDate>Wed, 26 Jul 2023 10:38:05 GMT</pubDate>
            <description><![CDATA[<pre><code class="language-jsx">// -----------------------댓글 추가 기능----------------------------

   // 댓글 추가 기능
      const handleCommentSubmit = async (e) =&gt; {
      e.preventDefault();
         try {
        // 댓글 작성 요청
        const accessToken = getCookie(&quot;accessToken&quot;);
        console.log(accessToken);
           await instance.post(
             `/api/comments`,
             {
             postId: id,
              content: commentText
            },
             {
              headers: {
                    Accept: &quot;*/*&quot;,
                     Authorization: `${accessToken}`,
                 },
            }
         )

         // 댓글 작성 후 폼 초기화
         setCommentText(&#39;&#39;);
    } catch (error) {
         console.log(&#39;댓글 작성 실패&#39;, error);
    }
}

-----------------------댓글 추가 기능----------------------------
</code></pre>
<pre><code class="language-jsx">
const accessToken = getCookie(&quot;accessToken&quot;);



const addCommentMutation = useMutation(
    (commentText) =&gt;
      instance.post(
        `/api/comments`,
        {
          postId: id,
          content: commentText,
        },
        {
          headers: {
            Accept: &#39;*/*&#39;,
            Authorization: `${accessToken}`,
          },
        }
      ),
    {
      onError: (error) =&gt; {
        console.log(&#39;댓글 작성 실패&#39;, error);
      },

      onSettled: () =&gt; {
        queryClient.invalidateQueries(&#39;comments&#39;);
      },
    }
  );


  const handleCommentSubmit = async (e) =&gt; {
    e.preventDefault();
    try {
      await addCommentMutation.mutateAsync(commentText);


      setCommentText(&#39;&#39;);
    } catch (error) {
      console.log(&#39;댓글 작성 실패&#39;, error);
    }
  };</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[좋아요 비동기 리액트로]]></title>
            <link>https://velog.io/@taejun-baek/%EC%A2%8B%EC%95%84%EC%9A%94-%EB%B9%84%EB%8F%99%EA%B8%B0-%EB%A6%AC%EC%95%A1%ED%8A%B8%EB%A1%9C</link>
            <guid>https://velog.io/@taejun-baek/%EC%A2%8B%EC%95%84%EC%9A%94-%EB%B9%84%EB%8F%99%EA%B8%B0-%EB%A6%AC%EC%95%A1%ED%8A%B8%EB%A1%9C</guid>
            <pubDate>Wed, 26 Jul 2023 10:28:51 GMT</pubDate>
            <description><![CDATA[<p>// -----------------------좋아요 기능----------------------------
//  const handleLikeButton = async () =&gt; {
//     try {
//         const accessToken = getCookie(&quot;accessToken&quot;);
//         console.log(accessToken);
//         const payload = {
//             postId:<code>${id}</code>,</p>
<p>//         }
//         await axios.post(
//             <code>${process.env.REACT_APP_SERVER_URL}/api/like</code>,
//             payload,
//             {
//                 headers: {
//                     &#39;Content-Type&#39;: &#39;application/json&#39;,
//                     Accept: &quot;<em>/</em>&quot;,
//                     Authorization: <code>${accessToken}</code>,
//                 },
//             }
//             );
//             // 좋아요 버튼 상태 변경
//             setIsLiked((prevState) =&gt; !prevState);</p>
<p>//     } catch (error) {
//         console.log(&#39;좋아요 처리 실패&#39;, error);
//     }
// };
// -----------------------좋아요 기능----------------------------</p>
<pre><code class="language-jsx">    const accessToken = getCookie(&quot;accessToken&quot;);

    const likeMutation = useMutation((payload) =&gt;
        axios.post(`${process.env.REACT_APP_SERVER_URL}/api/like`, payload, {
          headers: {
            &#39;Content-Type&#39;: &#39;application/json&#39;,
            Accept: &#39;*/*&#39;,
            Authorization: `${accessToken}`,
          },
        }),
      {
        onError: (error) =&gt; {
          console.log(&#39;좋아요 처리 실패&#39;, error);
        },
      }
    );

    const handleLikeButton = async () =&gt; {
      try {
        const payload = {
          postId: id,
        };

        // 좋아요 버튼의 상태를 변경
        setIsLiked((prevState) =&gt; !prevState);

        // 서버로 좋아요 요청 보내기
        await likeMutation.mutateAsync(payload);
      } catch (error) {
        console.log(&#39;좋아요 처리 실패&#39;, error);
      }
    };
</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[기존 비동기 통신에서 리액트 쿼리로]]></title>
            <link>https://velog.io/@taejun-baek/%EA%B8%B0%EC%A1%B4-%EB%B9%84%EB%8F%99%EA%B8%B0-%ED%86%B5%EC%8B%A0%EC%97%90%EC%84%9C-%EB%A6%AC%EC%95%A1%ED%8A%B8-%EC%BF%BC%EB%A6%AC%EB%A1%9C</link>
            <guid>https://velog.io/@taejun-baek/%EA%B8%B0%EC%A1%B4-%EB%B9%84%EB%8F%99%EA%B8%B0-%ED%86%B5%EC%8B%A0%EC%97%90%EC%84%9C-%EB%A6%AC%EC%95%A1%ED%8A%B8-%EC%BF%BC%EB%A6%AC%EB%A1%9C</guid>
            <pubDate>Tue, 25 Jul 2023 07:26:36 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/taejun-baek/post/992ce963-8ab2-4652-9cb8-037359c9393f/image.png" alt="">
위 와 같은 기존의 비동기 통신을 리액트 쿼리로 바꿔 보는 작업을 시작했습니다.
팀프로젝틀르 하면서 통신 방법의 통일이 필요했습니다.
아래는 리액트 쿼리로 바꿔본 위의 코드</p>
<p><img src="https://velog.velcdn.com/images/taejun-baek/post/17df9349-c34c-4d3d-a16f-8a2f37488fe6/image.png" alt=""></p>
<p>리액트 쿼리는 useMutation라는 훅으로 동작을 처리합니다.
주로 데이터를 변경하는 요청 (생성,업데이트,삭제)등을 보내고 서버의 응답을 처리할때 사용됩니다.
useMutation은 axios 또는 fetch와 같은 HTTP 클라이언트와 함께 사용되어 비동기 통신을 쉽게 처리하고, 응답 데이터를 관리하고 상태를 업데이트할 수 있도록 도와줍니다.</p>
<p>이로써 여러 개의 인자를 받을 수 있으며, 요청을 커스터마이징하고 응답을 처리하는 방법을 조정할 수 있습니다. </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[로그인 페이지 서버와 통신]]></title>
            <link>https://velog.io/@taejun-baek/%EB%A1%9C%EA%B7%B8%EC%9D%B8-%ED%8E%98%EC%9D%B4%EC%A7%80-%EC%84%9C%EB%B2%84%EC%99%80-%ED%86%B5%EC%8B%A0</link>
            <guid>https://velog.io/@taejun-baek/%EB%A1%9C%EA%B7%B8%EC%9D%B8-%ED%8E%98%EC%9D%B4%EC%A7%80-%EC%84%9C%EB%B2%84%EC%99%80-%ED%86%B5%EC%8B%A0</guid>
            <pubDate>Mon, 24 Jul 2023 16:28:46 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/taejun-baek/post/48bb3bf2-8c29-4643-81b3-512417a35f95/image.png" alt=""></p>
<p>서버와 연결을 위해 코드를 작성하고 아래 로그인을 시도해 보았지만 
로그인 버튼을 클릭하는 순간 리다이렉션이 되는 현상이 계속 되었다.
왜 이럴까 </p>
<p>시도1: 백엔드 서버와의 통신 점검
결과: 게시판의 GET은 되므로 아님</p>
<p>시도2: consloe.log 찍어보기
결과: 콘솔은 받는다.</p>
<p>시도3: 리다이렉션 된다는것을 파악하게됨</p>
<h4 id="원인">원인</h4>
<h4 id="form-태그는-기본적으로-제출되면-웹-페이지를-새로고침하거나-다른-페이지로-리디렉션되는-동작을-수행합니다-이-동작은-폼을-제출하는-것이-일반적인-웹-페이지의-기본-동작이기-때문에-브라우저가-기본적으로-수행합니다">form 태그는 기본적으로 제출되면 웹 페이지를 새로고침하거나 다른 페이지로 리디렉션되는 동작을 수행합니다. 이 동작은 폼을 제출하는 것이 일반적인 웹 페이지의 기본 동작이기 때문에 브라우저가 기본적으로 수행합니다.</h4>
<h4 id="그러나-react-애플리케이션에서는-주로-spa-single-page-application-방식을-사용하며-전통적인-웹-페이지처럼-매-요청마다-새로운-페이지를-로드하는-것이-아닌-단일-페이지-내에서-동적으로-컴포넌트를-교체하면서-사용자와-상호작용하는-방식을-취합니다">그러나 React 애플리케이션에서는 주로 SPA (Single Page Application) 방식을 사용하며, 전통적인 웹 페이지처럼 매 요청마다 새로운 페이지를 로드하는 것이 아닌, 단일 페이지 내에서 동적으로 컴포넌트를 교체하면서 사용자와 상호작용하는 방식을 취합니다.</h4>
<h4 id="이런-spa-방식에서는-폼-제출-시-페이지-리로딩-없이-ajax-요청을-사용하여-서버와-상호작용하고-페이지를-업데이트하는-것이-일반적입니다-이러한-동작을-위해서는-eventpreventdefault-함수를-사용하여-폼의-기본-제출-동작을-막아야-합니다">이런 SPA 방식에서는 폼 제출 시 페이지 리로딩 없이 AJAX 요청을 사용하여 서버와 상호작용하고, 페이지를 업데이트하는 것이 일반적입니다. 이러한 동작을 위해서는 event.preventDefault() 함수를 사용하여 폼의 기본 제출 동작을 막아야 합니다.</h4>
<h4 id="따라서-loginstate-함수-내에서-폼을-제출할-때-eventpreventdefault를-호출하지-않았기-때문에-폼이-기본적인-제출-동작을-수행하고-페이지가-새로고침되면서-spa-방식의-장점을-잃어버리는-문제가-발생했던-것입니다">따라서 LoginState 함수 내에서 폼을 제출할 때 event.preventDefault()를 호출하지 않았기 때문에, 폼이 기본적인 제출 동작을 수행하고 페이지가 새로고침되면서 SPA 방식의 장점을 잃어버리는 문제가 발생했던 것입니다.</h4>
<h4 id="eventpreventdefault를-추가하여-폼-제출의-기본-동작을-막으면-페이지가-리로딩되지-않고-비동기-요청이-수행되도록-할-수-있습니다">event.preventDefault()를 추가하여 폼 제출의 기본 동작을 막으면, 페이지가 리로딩되지 않고 비동기 요청이 수행되도록 할 수 있습니다.</h4>
<pre><code class="language-jsx">  const LoginState = async (event) =&gt; { 
    event.preventDefault();
    console.log(process.env.REACT_APP_SERVER_URL)
    try {</code></pre>
<p>따라서 함수 내에 event.preventDefault();를 추가 해줬더니 깔끔하게 로그인이 됌</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[오늘의집 클론코딩 ]]></title>
            <link>https://velog.io/@taejun-baek/%EC%98%A4%EB%8A%98%EC%9D%98%EC%A7%91-%ED%81%B4%EB%A1%A0%EC%BD%94%EB%94%A9</link>
            <guid>https://velog.io/@taejun-baek/%EC%98%A4%EB%8A%98%EC%9D%98%EC%A7%91-%ED%81%B4%EB%A1%A0%EC%BD%94%EB%94%A9</guid>
            <pubDate>Mon, 24 Jul 2023 15:59:34 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/taejun-baek/post/6b39029f-be88-4503-b7a7-8861906f756c/image.png" alt=""></p>
<p>어제 로그인 페이지에 이어 오늘은 집사진이라는 상세페이지를 구현 했습니다.
틱히 어려웠던 점은 오른쪽에 있는 4개의 버튼들의 위치를 레이아웃하는것이 힘들었습니다.</p>
<pre><code class="language-jsx">
  return (
    &lt;PicTotal&gt;
        &lt;PicPadding&gt;
            &lt;PicTop&gt;
                &lt;CardWidth&gt;
                    &lt;CardTop&gt;
                        &lt;DetailTag&gt;
                           &lt;DetailTagText&gt;
                                &lt;TagTextButton&gt;모던스타일&lt;/TagTextButton&gt;
                                &lt;TagTextButton&gt;아파트&lt;/TagTextButton&gt;
                            &lt;/DetailTagText&gt; 
                        &lt;/DetailTag&gt;
                        &lt;CardPics&gt;
                             &lt;CardPic src=&quot;https://image.ohou.se/i/bucketplace-v2-development/uploads/cards/snapshots/164890162254034673.jpeg?w=1440&quot;  /&gt;
                        &lt;/CardPics&gt;
                    &lt;/CardTop&gt;
&lt;CustomHr2 /&gt;
                    &lt;div&gt;
                        &lt;CommentDiv&gt;
                            &lt;CommentText&gt;
                                댓글
                                &lt;CommentNumber&gt;
                                    46
                                &lt;/CommentNumber&gt;
                            &lt;/CommentText&gt;
                            &lt;CommentButDiv6&gt;
                                &lt;CommentButDiv5&gt;
                                    &lt;CommentEmoticon&gt;
                                        &lt;CommentEmoticonPic src=&quot;https://image.ohou.se/i/bucketplace-v2-development/uploads/cards/snapshots/164890162254034673.jpeg?w=1440&quot;  /&gt;
                                    &lt;/CommentEmoticon&gt;
                                    &lt;CommentButDiv4&gt;
                                        &lt;CommentButDiv3&gt;
                                            &lt;CommentButDiv2&gt;
                                                &lt;CommentLine contenteditable=&quot;true&quot; data-placeholder=&quot;칭찬과 격려의 댓글은 작성자에게 큰 힘이 됩니다:)&quot; size=&#39;44&#39; isEmpty={isEmpty} onInput={handleChange}&gt;

                                                &lt;/CommentLine&gt;
                                                &lt;CommentButDiv&gt;
                                                    &lt;CommentBtn&gt;
                                                        입력
                                                    &lt;/CommentBtn&gt;
                                                &lt;/CommentButDiv&gt;
                                            &lt;/CommentButDiv2&gt;
                                        &lt;/CommentButDiv3&gt;
                                    &lt;/CommentButDiv4&gt;
                                &lt;/CommentButDiv5&gt;
                            &lt;/CommentButDiv6&gt;
                        &lt;/CommentDiv&gt;

                    &lt;/div&gt;
                &lt;/CardWidth&gt;

                &lt;SideButtonPosition&gt;
                    &lt;SideButtonSticky&gt;
                        &lt;SideButtonControl&gt;
                            &lt;SideButtonBox&gt;
                                &lt;SideButton&gt;
                                    &lt;SideButtonSpan onClick={handleHeartClick}&gt;
                                        &lt;SideButtonHeart &gt;
                                        {isFilledHeart ? &lt;AiFillHeart  color = &quot;#43C5F0&quot;size={23}/&gt; : &lt;AiOutlineHeart  size={23}/&gt;}


                                        &lt;/SideButtonHeart&gt;
                                    &lt;/SideButtonSpan&gt;
                                    &lt;SideButtonSpanNumber&gt;
                                        180
                                    &lt;/SideButtonSpanNumber&gt;
                                &lt;/SideButton&gt;

                                &lt;SideButton&gt;
                                    &lt;SideButtonSpan onClick={handleBookClick}&gt;
                                        &lt;SideButtonHeart&gt;
                                        {isFilledBook ? &lt;FaBookmark  color = &quot;#43C5F0&quot;size={23}/&gt; : &lt;FaRegBookmark  size={23}/&gt;}


                                        &lt;/SideButtonHeart&gt;
                                    &lt;/SideButtonSpan&gt;
                                    &lt;SideButtonSpanNumber&gt;
                                        180
                                    &lt;/SideButtonSpanNumber&gt;
                                &lt;/SideButton&gt;
&lt;CustomHr /&gt;
                                &lt;SideButton&gt;
                                    &lt;SideButtonSpanLow&gt;
                                        &lt;SideButtonHeart&gt;
                                            &lt;SlBubble size={23}/&gt;
                                        &lt;/SideButtonHeart&gt;
                                    &lt;/SideButtonSpanLow&gt;
                                    &lt;SideButtonSpanNumber&gt;
                                        180
                                    &lt;/SideButtonSpanNumber&gt;
                                &lt;/SideButton&gt;

                                &lt;SideButton&gt;
                                    &lt;SideButtonSpanLow&gt;
                                        &lt;SideButtonHeart&gt;
                                            &lt;FiShare2 size={23}/&gt;
                                        &lt;/SideButtonHeart&gt;
                                    &lt;/SideButtonSpanLow&gt;
                                    &lt;SideButtonSpanNumber&gt;
                                        180
                                    &lt;/SideButtonSpanNumber&gt;
                                &lt;/SideButton&gt;

                            &lt;/SideButtonBox&gt;
                        &lt;/SideButtonControl&gt;
                    &lt;/SideButtonSticky&gt;
                &lt;/SideButtonPosition&gt;
            &lt;/PicTop&gt;
        &lt;/PicPadding&gt;
    &lt;/PicTotal&gt;
  ) 
}</code></pre>
<h3 id="그리고-엄청난-양의-div지옥">그리고 엄청난 양의 Div지옥</h3>
<p>CSS는 생각대로 잘될때는 너무나 재미있지만 하나가 틀어지면 레이아웃이 산으로 가는 현상은
끊었던 담배를 생각나게 합니다.
그래도 재밌어 ㅋㅋ</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Git 협업 규칙]]></title>
            <link>https://velog.io/@taejun-baek/Git-%ED%98%91%EC%97%85-%EA%B7%9C%EC%B9%99</link>
            <guid>https://velog.io/@taejun-baek/Git-%ED%98%91%EC%97%85-%EA%B7%9C%EC%B9%99</guid>
            <pubDate>Mon, 24 Jul 2023 04:07:41 GMT</pubDate>
            <description><![CDATA[<h2 id="git-협업-규칙">Git 협업 규칙</h2>
<h3 id="git-flow-전략"><code>Git Flow</code> 전략</h3>
<ul>
<li>main - dev - front, back - feature_개인단위</li>
<li>깃 저장소 관리</li>
</ul>
<ol>
<li>프론트 저장소 : <code>origin</code> : master / dev / feature_</li>
<li>백 저장소 : <code>origin</code> : master / dev / feature_</li>
<li><del>개별 저장소 : <code>origin</code> : (fork) master / dev / feature</del></li>
</ol>
<ul>
<li><strong><del>개인 레포에서 작업 후 병합 순서</del></strong></li>
</ul>
<ol>
<li><p>개인 작업 :
<code>add , commit</code>
로컬의 feature 브랜치에서 작업을 진행.
<code>git checkout dev</code>
dev 브랜치로 이동한다
<code>git merge feature</code>
로컬의 feature 브랜치를 dev 브랜치에 병합함</p>
</li>
<li><p>팀 레포 병합 전 충돌 해결 :
<code>git fetch upstream</code>
팀 back 레포를 로컬 dev에 fetch한다.
<code>git merge upstream/dev</code>
그 후 팀 레포의 dev 브랜치를 로컬의 dev에 병합하여
발생하는 충돌을 해결한다</p>
</li>
<li><p>개인 레포에 로컬 dev 브랜치를 push한다
<code>git push origin dev</code></p>
</li>
<li><p>Merge Request 신청 : 깃랩
개인 레포 dev 브랜치 → 팀 back 레포 dev 브랜치에 merge request를 생성한다.</p>
</li>
</ol>
<h3 id="branch-정의-main-dev-feature">Branch <strong>정의</strong> <code>main</code> <code>dev</code> <code>feature</code></h3>
<ul>
<li><code>main</code>( = master) :
바로 product로 release(배포) 할 수 있는 브랜치
하나의 <code>main</code>만 사용, 배포 시 Tag 및 업데이트 내용 추가 작성, 추가 생성하지 않습니다.</li>
<li><code>dev</code> :
product로 release 할 준비가 된 가장 안정적인 브랜치로 개발이 완료된 상태라면 <code>main</code> 브랜치로 merge합니다.
하나의 <code>dev</code>만 사용, 추가 생성하지 않습니다.</li>
<li><code>feature</code> :
새로운 기능을 추가할 때 사용하는 브랜치로 <code>dev</code> 브랜치에서 분기하여 진행되며, 개발이 완료된 기능은 <code>dev</code> 브랜치로 merge합니다.
해당 feature 업무 담당자가 최신 버전의 <code>dev</code>에서 <code>feature/&lt;포지션-기능명&gt;</code>으로 생성합니다.</li>
</ul>
<h3 id="커밋-메시지-규칙-type--subject">커밋 메시지 규칙 <code>Type : Subject</code></h3>
<ul>
<li><strong>Type</strong></li>
</ul>
<p><code>**feat</code> : 새로운 기능**</p>
<p><code>**fix</code> : 버그 수정에 대한 커밋**</p>
<p><code>**patch</code> : 기능 부분 코드 수정**</p>
<p><code>refactor</code> : 코드 리팩토링 수정</p>
<p><code>rm</code> : 기능 삭제</p>
<p><code>docs</code> : 문서 수정</p>
<p><code>style</code>: 스타일링 수정</p>
<ul>
<li>유의미한 코드 단위/기능 완료 시에 커밋</li>
<li>Reviewer, Assignee 지정</li>
</ul>
<h3 id="mr-규칙-title--description">MR 규칙 <code>Title</code> + <code>Description</code></h3>
<ul>
<li>TITLE : <code>Type : Subject</code></li>
<li>Description : <code>작업내용 작성</code></li>
<li>Reviewer, Assignee 지정</li>
</ul>
<hr>
]]></description>
        </item>
        <item>
            <title><![CDATA[미니프로젝트 클론코딩]]></title>
            <link>https://velog.io/@taejun-baek/%EB%AF%B8%EB%8B%88%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%ED%81%B4%EB%A1%A0%EC%BD%94%EB%94%A9</link>
            <guid>https://velog.io/@taejun-baek/%EB%AF%B8%EB%8B%88%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%ED%81%B4%EB%A1%A0%EC%BD%94%EB%94%A9</guid>
            <pubDate>Sun, 23 Jul 2023 16:03:23 GMT</pubDate>
            <description><![CDATA[<p>이번주 오늘의집 클론코딩을 시작했다
프론트와 백엔드로 나누어서
작업을 진행했고
나는 그중에 우선 로그인을 구현했다. </p>
<h4 id="첫째날-완성물">첫째날 완성물</h4>
<p>강의로만 클론 코딩을 해왔는데 실제로 내가 사이트를 보고 클론코딩을 해보는건 처음이었다.
이전 프로젝트에서는 나만의 생각으로 그려왔던것에 반해 이미 만들어져 있는것을 똑같이 흉내내는건
상당히 손이 많이 갔다. CSS 스킬 향상에 좋을듯... 재밌어 ㅋㅋㅋ</p>
<p><img src="https://velog.velcdn.com/images/taejun-baek/post/883688ba-355e-4b4f-8392-c2212b550dfa/image.png" alt=""></p>
<pre><code class="language-jsx">import React from &quot;react&quot;;
import { styled } from &quot;styled-components&quot;;
import Card from &#39;react-bootstrap/Card&#39;;

const LogIn = () =&gt; {
  return (
    &lt;LoginMain&gt;
    &lt;LoginLayout&gt;
      &lt;Layout&gt;
      &lt;h2&gt;
        &lt;img
        src=&quot;/img/todaylogo.png&quot;
        alt=&quot;로고 이미지&quot;
        style={{ marginBottom: &#39;30px&#39;,
        cursor: &#39;pointer&#39;, marginTop:&#39;40px&#39;,width: &#39;150px&#39;, height: &#39;50px&#39; }}
      /&gt;
      &lt;/h2&gt;

      &lt;form style={{width: &#39;300px&#39;}}&gt;
        &lt;LoginInput type=&quot;text&quot; placeholder=&quot;이메일&quot;/&gt;
        &lt;LoginInput type=&quot;text&quot; placeholder=&quot;비밀번호&quot;/&gt;

         &lt;LoginBtn&gt;로그인&lt;/LoginBtn&gt;

        &lt;section style={{display: &#39;block&#39;, textAlign: &#39;center&#39;}}&gt;
          &lt;LoginText&gt;비밀번호 재설정&lt;/LoginText&gt;
          &lt;LoginText &gt;회원가입&lt;/LoginText&gt;
        &lt;/section&gt;
      &lt;/form&gt;

      &lt;section style={{display: &#39;block&#39;, textAlign: &#39;center&#39;}}&gt;
          &lt;LoginSns&gt;SNS계정으로 간편 로그인/회원가입&lt;/LoginSns&gt;
          &lt;Card.Img 
  variant=&quot;top&quot;
  src=&quot;/img/facebook.png&quot;
  style={{
    objectFit: &#39;cover&#39;,
    width: &#39;1.3cm&#39;,
    height: &#39;1.3cm&#39;,
    borderRadius: &#39;50%&#39;,
    marginRight: &#39;20px&#39;, 
  }}
/&gt;
&lt;Card.Img
  variant=&quot;top&quot;
  src=&quot;/img/kakao.png&quot;
  style={{
    objectFit: &#39;cover&#39;,
    width: &#39;1.3cm&#39;,
    height: &#39;1.3cm&#39;,
    borderRadius: &#39;50%&#39;,
    marginRight: &#39;20px&#39;, 
  }}
/&gt;
&lt;Card.Img
  variant=&quot;top&quot;
  src=&quot;/img/naver.png&quot;
  style={{
    objectFit: &#39;cover&#39;,
    width: &#39;1.3cm&#39;,
    height: &#39;1.3cm&#39;,
    borderRadius: &#39;50%&#39;,
    marginRight: &#39;20px&#39;, 
  }}
/&gt;
        &lt;/section&gt;
        &lt;p style={{    marginTop: &#39;24px&#39;,
    color: &#39;rgb(194, 200, 204)&#39;,
    fontSize: &#39;14px&#39;,
    lineHeight: &#39;18px&#39;,
    cursor: &#39;pointer&#39;}}&gt;로그인에 문제가 있으신가요?&lt;/p&gt;
     &lt;LoginUnuserText&gt;비회원 주문 조회하기&lt;/LoginUnuserText&gt;

      &lt;/Layout&gt;

    &lt;LoginFooter&gt;© bucketplace, Co., Ltd.. All Rights Reserved&lt;/LoginFooter&gt;
    &lt;/LoginLayout&gt;
    &lt;/LoginMain&gt;
  );
};

export default LogIn;</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[::before와::after 의 이해]]></title>
            <link>https://velog.io/@taejun-baek/before%EC%99%80after-%EC%9D%98-%EC%9D%B4%ED%95%B4</link>
            <guid>https://velog.io/@taejun-baek/before%EC%99%80after-%EC%9D%98-%EC%9D%B4%ED%95%B4</guid>
            <pubDate>Sun, 23 Jul 2023 11:20:52 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/taejun-baek/post/bd7155b9-e903-487b-b797-360b8b7b21c6/image.png" alt=""></p>
<p>여기 ::after는 뭘까 <img src="https://velog.velcdn.com/images/taejun-baek/post/ec559be2-3a1e-4157-99cf-66fbf2c668be/image.png" alt=""></p>
<p>모던 스타일과 아파트 사이에 가상요소를 만들어 준다 </p>
<h4 id="가상요소란">가상요소란</h4>
<p>본문</p>
<ol>
<li>가상요소 ::before, ::after의 정의</li>
</ol>
<p>1.1) 가상 요소(Pseudo-Element)란?</p>
<p>– 가상클래스(Pseudo-Class)는,별도의 class를 지정하지 않아도 지정한 것 처럼요소를 선택할 수 있습니다.
– 가상요소(Pseudo-Element)는, 가상클래스처럼 선택자(selector)에 추가되며,
존재하지 않는 요소를 존재하는 것처럼 부여하여 문서의 특정 부분 선택이 가능합니다.<img src="https://velog.velcdn.com/images/taejun-baek/post/9eb15d78-7ee0-48a8-bcf8-5986ac774aae/image.png" alt=""></p>
<h4 id="실제-사용-적용">실제 사용 적용</h4>
<p>이렇게 단어마다 |를 만들어줘 단어 간의 구분을 만들어준다.
맨 마지막 단어에는 빼고 싶으므로</p>
<pre><code>&amp;:last-child::after {
    display: none;
  }</code></pre><p>  를 넣어주면 완벽</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[리액트 쿼리 refetch (실시간업데]]></title>
            <link>https://velog.io/@taejun-baek/%EB%A6%AC%EC%95%A1%ED%8A%B8-%EC%BF%BC%EB%A6%AC</link>
            <guid>https://velog.io/@taejun-baek/%EB%A6%AC%EC%95%A1%ED%8A%B8-%EC%BF%BC%EB%A6%AC</guid>
            <pubDate>Wed, 19 Jul 2023 20:16:40 GMT</pubDate>
            <description><![CDATA[<p>왜 댓글을 작성했는데 새로고침을 하기 전까지 업데이트가 되지 않는걸까?</p>
<h1 id="1번째-시도">1번째 시도</h1>
<h3 id="결과실패">결과:실패</h3>
<p>onMutate 옵션을 사용하여 mutate 함수가 호출된 직후에 새로운 데이터를 다시 가져오도록 해보았다
실제로는 onMutate 옵션을 사용하는 것으로만으로는 댓글을 작성하자마자 즉시 업데이트되지는 않습니다. onMutate 옵션은 mutate 함수가 호출된 직후에 실행되는 콜백 함수이지만, useQuery 훅이 리턴하는 data는 axios.get 요청을 기반으로 한 데이터로 초기화됌</p>
<pre><code class="language-jsx">   const { data: cardData, isLoading, isError, refetch } = useQuery(
        [&#39;card&#39;, id],
        () =&gt; axios.get(`http://52.79.242.223/api/posts/${id}`).then((res) =&gt; res.data),
        {
            staleTime: 10000, // 10초 동안 만료된 데이터를 보여줌
            onMutate: () =&gt; {
                // 댓글 작성 시 새로운 데이터를 가져오도록 mutate 함수 호출
                refetch();
            },
        }
    );</code></pre>
<h1 id="2번째-시도">2번째 시도</h1>
<h3 id="결과성공">결과:성공</h3>
<p>onMutate 옵션은 주로 mutate 함수가 성공적으로 호출되었을 때 데이터를 업데이트하는 등의 사후 작업을 수행할 때 사용됩니다. 따라서 댓글을 작성하자마자 즉시 업데이트를 보고 싶다면, 아래와 같이 onMutate 옵션을 사용하는 것이 아닌, 댓글 작성 성공 후에 refetch 함수를 호출하여 새로운 데이터를 가져오도록 해야 합니다:</p>
<pre><code class="language-jsx">    // 댓글 작성 성공 후 호출되는 함수
    const handleCommentSubmit = async (e) =&gt; {
        e.preventDefault();
        try {
            // 댓글 작성 요청
            const accessToken = getCookie(&quot;accessToken&quot;);
            console.log(accessToken);
            await axios.post(
                `http://52.79.242.223/api/comments`,
                {
                    postId: id,
                    content: commentText
                },
                {
                    headers: {
                        Accept: &quot;*/*&quot;,
                        Authorization: `${accessToken}`,
                    },
                }
            );

            // 댓글 작성 후 새로운 데이터를 가져오도록 refetch 함수 호출
            refetch();

            // 댓글 작성 후 폼 초기화
            setCommentText(&#39;&#39;);
        } catch (error) {
            console.log(&#39;댓글 작성 실패&#39;, error);
        }
    };</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[리액트 쿼리 쿠키에서  토큰 받기]]></title>
            <link>https://velog.io/@taejun-baek/%EB%A6%AC%EC%95%A1%ED%8A%B8-%EC%BF%BC%EB%A6%AC-%EC%BF%A0%ED%82%A4%EC%97%90%EC%84%9C-%ED%86%A0%ED%81%B0-%EB%B0%9B%EA%B8%B0</link>
            <guid>https://velog.io/@taejun-baek/%EB%A6%AC%EC%95%A1%ED%8A%B8-%EC%BF%BC%EB%A6%AC-%EC%BF%A0%ED%82%A4%EC%97%90%EC%84%9C-%ED%86%A0%ED%81%B0-%EB%B0%9B%EA%B8%B0</guid>
            <pubDate>Wed, 19 Jul 2023 16:55:00 GMT</pubDate>
            <description><![CDATA[<pre><code class="language-jsx">// 쿠키에서 토큰 받기
function getCookie(cookieName){
    var cookieValue=null;
    if(document.cookie){
        var array=document.cookie.split((escape(cookieName)+&#39;=&#39;));
        if(array.length &gt;= 2){
            var arraySub=array[1].split(&#39;;&#39;);
            cookieValue=unescape(arraySub[0]);
        }
    }
    return cookieValue;
}
</code></pre>
]]></description>
        </item>
    </channel>
</rss>