기록
  • Day 7 - CSS 작업 (完)
    2024년 09월 12일 19시 47분 12초에 업로드 된 글입니다.
    작성자: 삶은고구마
    • 누이터 스타일링하기
    • 버그 수정

    1.누이터 스타일링하기

    css를 중점으로 다루는 프로젝트는 아닌만큼 최대한 간결하고 깔끔하게만 설명한듯 하다.

    (저자 역시 그렇게 말했다.)

     

    1)package.json  수정

    스타일적용을 위해 맨 먼저 할 것은 스타일 관련 패키지를 설치 하는 것이다.

    폰트어썸의 아이콘을 사용 (유료,무료 모두있음)

     

    package.json

    "dependencies": {
        "@fortawesome/fontawesome-free":"^5.14.0",
        "@fortawesome/fontawesome-svg-core":"^1.2.30",
        "@fortawesome/free-brands-svg-icons":"^5.14.0",
        "@fortawesome/free-regular-svg-icons":"^5.14.0",
        "@fortawesome/free-solid-svg-icons":"^5.14.0",
        "@fortawesome/react-fontawesome":"^0.1.11",

     

    터미널

    npm install

     

     

     

    2)깃허브 커밋 이력 참고하여 styles.css파일 만들기

    style.css 커밋주소

    https://vo.la/kBejt

     

     

    3)styles.css 임포트

    src 폴더에 styles.css 파일 만든 후 깃허브 수정 이력을 참고하여

    styles.css를 모두 채우고 index.js파일에서 styles.css임포트

     

    index.js

    import "./styles.css";

     

     

     

    4)Auth,AuthForm 컴포넌트에 스타일 작업

    AuthForm.js

     return(
                <>
                
                    <form onSubmit={onSubmit} className="container">
                        <input 
                            name="email" 
                            type = "email" 
                            placeholder="Email" 
                            required 
                            value={email} 
                            onChange={onChange}
                            className="authInput"
                            />
                        <input 
                            name="password" 
                            type = "password" 
                            placeholder="Password" 
                            required
                            value={password}
                            onChange={onChange}
                            className="authInput"
                            />
                        <input 
                            type = "submit" 
                            value = {newAccount ? "Create Account" : "Log in"}
                            className="authInput authSubmit"
                            />
                        <br/>
                        {error && <span className="authError">{error}</span>}
                    </form>
                    
                        <span onClick={toggleAccount} className="authSwitch">
                            {newAccount ? "Sign In" : "Create Account"}
                        </span>
                       
                    </>
                
            );

     

    Auth.js

    import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
    import { faTwitter,faGoogle,faGithub } from "@fortawesome/free-brands-svg-icons";
    ..
    return(
            <div className="authContainer">
                <FontAwesomeIcon
                    icon={faTwitter}
                    color={"#04AAFF"}
                    size="3x"
                    style={{marginBottom:30}}
                />
                <AuthForm/>
                <div className="authBtns">
                    <br/>
                    <button onClick={onSocialClick} name="google" className="authBtn">
                        Continue with Google <FontAwesomeIcon icon={faGoogle}/></button>
                    <button onClick={onSocialClick} name="github" className="authBtn">
                        Continue with Github <FontAwesomeIcon icon={faGithub}/></button>
                </div>
            </div>
        );

     

     

     

    5)Router 컴포넌트에 스타일 적용

    로그인 한 상태에서 적용해보자.

    Router.js
     return(
            <Router>
              {isLoggedIn && <Navigation userObj={userObj}/>}
              <div
                style={{
                  maxWidth:890,
                  width:"100%",
                  margin:"0 auto",
                  marginTop:80,
                  display:"flex",
                  justifyContent:"center",
    
                }}>
                    <Routes> 
            {isLoggedIn ? (
              <>
                <Route exact path="/" element={<Home userObj={userObj}/>}/>
                <Route exact path="/profile" element={<Profile refreshUser={refreshUser} userObj={userObj}/>}/>
              </>
            ) : (
              <Route exact path="/" element={<Auth />}></Route>
            )}
            {/* <Route path="*" element={<Navigate replace to="/" />} />     */}
          </Routes>
          </div>
            </Router>    
        );

     

    책에서는 기존 <></>태그를 <div></div>태그로 변경하고 div에 스타일을 적용하라 하는데

    그대로 하면 에러가 난다. 

    이 코드에서 switch를 routes로 바꾼 이유와 같은데, 

    react-router-dom v6부터는 switch를 사용하지 않는다. 대신 Routes로 사용한다.

    Routes의 자식은 Route만 가능.

    <Routes>

     <Route/>

     <Route/>

    </Routes>

    이런식으로 사용한다.

    혹시나 싶어서 Routes안의 div를 외부로 빼내고(윗쪽에) 실행하니 잘 된다.

    구글링해보니까 다른 분들도 이렇게 처리했다..ㅠㅠ

     

    해결 및 참조 링크

    https://velog.io/@nemo/react-error-routes

     

    [React 에러 일지] ... is not a <Route> component. All component children of <Routes> must be a <Route> or <React.Fragment>

    🚫 \[...] is not a <Route> component. All component children of <Routes> must be a <Route> or <React.Fragment>react-router-dom v6부터는,Switc

    velog.io

     

    실행결과

     

     

    6)Home 컴포넌트에 스타일 적용

    return (
            <div className="container">
                {/* 기존 폼을 컴포넌트화 함 */}
                <NweetFactory userObj={userObj}/>
             <div style={{marginTop:30}}>
                    {nweets.map((nweet)=>(
                        <Nweet 
                            key = {nweet.id} 
                            nweetObj={nweet}
                            isOwner={nweet.creatorId === userObj.uid}
                        />
                    ))}
                </div>
            </div>
        );

     

     

     

    7)Navigation 컴포넌트 스타일 적용하기

    폰트어썸 아이콘 추가 

    ul 엘리먼트를 flex로 중앙 정렬

    프로필은 이름이 있는 경우엔 이름 출력, 아닌 경우엔 Profile만 출력 (삼항연산자)

     

    Navigation.js

    return(
            <nav>
                <ul style ={{display:"flex", justifyContent:"center",marginTop:50}}>
                    <li>
                        <Link to="/" style = {{marginRight:10}}>
                        <FontAwesomeIcon icon={faTwitter} color={"#04AAFF"} size="2x"/>
                        </Link>
                    </li>
                    <li>
                        <Link 
                            to="/profile"
                            style={{
                                marginLeft:10,
                                display:"flex",
                                msFlexDirection:"column",
                                alignItems:"center",
                                fontSize:12,
                            }}
                        >
                            <FontAwesomeIcon icon={faUser} color={"#04AAFF"} size="2x"/>
                            <span style={{marginTop:10}}>
                                {userObj.displayName 
                                ? `${userObj.displayName}의 Profile`
                                :"Profile"}            
                            </span>                  
                        </Link>
                    </li>
                </ul>
            </nav>
        );

     

     

    8)Nweet,NweetFactory 컴포넌트에 스타일 적용

    폼을 담당하는 팩토리부터 수정.

    현재는 아무 글자를 입력하지 않아도 트윗을 할 수 있도록 되어있는데 이것을 막는 코드를 추가할 것이다.

     

    NweetFactory.js

    import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
    import { faPlus, faTimes } from "@fortawesome/free-solid-svg-icons"; 
    ..
    const onSubmit = async (event) =>{
       //새로고침 방지 
       event.preventDefault();
     //트윗을 단 한글자도 하지 않는 경우 전송하지 않음
       if(nweet==="")
       {
           return;
       }
       ..
       return (
            <>
            <form onSubmit={onSubmit} className="factoryForm">
    
                <div className="factoryInput__container">
                    <input
                        className="factoryInput__input"
                        value={nweet}
                        onChange={onChange}
                        type="text"
                        placeholder="What's on your mind?"
                        maxLength={120}
                    />
                    <input type="submit" value="&rarr;" className="factoryInput__arrow"/>
                </div>
                <label htmlFor="attach-file" className="factoryInput__label">
                    <span>Add Photos</span>
                    <FontAwesomeIcon icon={faPlus}/>
                </label>
                <input
                    id="attach-file"
                    type="file" 
                    accept ="image/*" 
                    onChange={onFileChange}
                    style={{
                        opacity:0,
                    }}
                />
                {attachment && (
                    <div className="factoryForm__attachment">
                        <img 
                            src={attachment} 
                            style={{
                                backgroundImage:attachment,
                            }}
                        />
                        <div className="factoryForm__clear" onClick={onClaerAttachment}>
                            <span>Remove</span>
                            <FontAwesomeIcon icon={faTimes}/>
                        </div>
                    </div> 
                )}
            </form>
            </>
        );

     

     

     

    Nweet.js

    import { dbService,storageService } from "fbase";
    import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
    import { faTrash,faPencilAlt } from "@fortawesome/free-solid-svg-icons";
    
    const Nweet = ({nweetObj,isOwner})=>{
        const onDeleteClick = async() =>{
            const ok = window.confirm("삭제하시겠습니까?");
            // console.log(ok);
    
            //확인을 눌렀을 경우..
            if(ok)
            {
                // console.log(nweetObj.id);
                await dbService.doc(`nweets/${nweetObj.id}`).delete();
                // console.log(data);
                if(nweetObj.attachmentUrl !=="")
                    await storageService.refFromURL(nweetObj.attachmentUrl).delete();
            }
        }
    
        return(
            <div className="nweet">
                
                <h4>{nweetObj.text}</h4>
                {nweetObj.attachmentUrl && (
                    <img src={nweetObj.attachmentUrl} width="100px" height="100px"/>
                )}
                {isOwner &&(
                    <div className="nweet__actions">
                        <span onClick={onDeleteClick}>
                            <FontAwesomeIcon icon={faTrash}/>
                        </span>
                    </div>          
                )}
            </div>
        );
    };
    export default Nweet;

     

     

    참고로 실제 트위터는 먼저 썼던 트윗을 수정할 순 없고 오로지 삭제만 가능하다.

    책에는 수정기능도 있는데 일단 삭제기능만 구현했다. (수정은 추후 클론코딩할 예정)

     

    9)Profile 컴포넌트에 스타일 적용

    profile.js

    return(
            <div className="container">
            <form onSubmit={onSubmit} className="profileForm">
            <input 
                onChange={onChange} 
                type="text" 
                placeholder="display name" 
                value={newDisplayName}
                autoFocus
                className="formInput"
            />
            <input 
                type="submit" 
                value="update profile"
                className="formBtn"
                style={{
                    marginTop:10,
                }}
            />
            </form>
                <span className="formBtn cancelBtn logOut" onClick={onLogOutClick}>
                    Log Out
                </span>
            </div>
        );

     

     

    10)브라우저 명 바꾸기

    현재 탭 이름은 React App 

    이것도 수정이 가능하다..!

     

    index.html

    <!-- <title>React App</title> -->
        <title>Nwitter</title>
      </head>

     

    2.버그 수정

     

    1)트윗 순서가 엉키는 버그

    간혹 트윗 순서가 엉키는 현상이 발생..

    트윗을 화면에 출력하는 Home 컴포넌트에 문제가 있을 것이다.

    이 컴포넌트를 살펴보면 파이어스토어에서 트윗 데이터를 가져 온 다음

    화면에 렌더링할 때 트윗 데이터를 가져오기만 할 뿐 정렬은 하지 않았다.

    orderby를 적용하자. 

    생성시간을 내림차순 정렬하였다.

    이렇게 하면 최신 글이 맨 위로 올라오고 오래된 글일수록 밑으로 내려간다.

     

     

    Home.js

     //실시간 db 위한 onSnapshot함수적용
        useEffect(()=>{
           dbService.collection("nweets")
           .orderBy("createdAt","desc")
           .onSnapshot((snapshot)=>{
            const newArray = snapshot.docs.map((document)=>({
                id : document.id,...document.data(),
            }));
            setNweets(newArray);
           });
        },[]);

     

     

    2)사진 선택 취소 버그

    이 에러는 사진을 한 번 선택해서 등록했다가 다시 add photo를 눌러서 선택창을 열은 후 취소할 때 발생한다.

    ERROR
    Failed to execute 'readAsDataURL' on 'FileReader': parameter 1 is not of type 'Blob'. TypeError: Failed to execute 'readAsDataURL' on 'FileReader': parameter 1 is not of type 'Blob'. at onFileChange (http://localhost:3001/nwitter/static/js/bundle.js:634:12) at HTMLUnknownElement.callCallback (http://localhost:3001/nwitter/static/js/bundle.js:72537:18) at Object.invokeGuardedCallbackDev (http://localhost:3001/nwitter/static/js/bundle.js:72581:20) at invokeGuardedCallback (http://localhost:3001/nwitter/static/js/bundle.js:72638:35) at invokeGuardedCallbackAndCatchFirstError (http://localhost:3001/nwitter/static/js/bundle.js:72652:29) at executeDispatch (http://localhost:3001/nwitter/static/js/bundle.js:76795:7) at processDispatchQueueItemsInOrder (http://localhost:3001/nwitter/static/js/bundle.js:76821:11) at processDispatchQueue (http://localhost:3001/nwitter/static/js/bundle.js:76832:9) at dispatchEventsForPlugins (http://localhost:3001/nwitter/static/js/bundle.js:76841:7) at http://localhost:3001/nwitter/static/js/bundle.js:77001:16

     

    발생 이유는 파일 첨부가 안 되었을 때 파일을 읽도록 코드가 작성되어있어서 그런 것이다.

    파일이 첨부되었을때만 읽도록 수정하면 될것이다. [true상태일때만]

    기존에 실행되던 코드를 if문 조건안에만 넣으면 된다.

     

    NweetFactory.js

    // reader.readAsDataURL(theFile);
            //파일이 첨부 안되었을 때는 읽지 않도록 조건을 수정한다.
            if(Boolean(theFile))
            {
                reader.readAsDataURL(theFile);
            }

     

     

    3)수정 된 누이터 배포하기

    터미널

    npm run deploy

     

    클론코딩 마무리 버전으로 깃허브에 커밋.

     

     

    +

    리액트는 학원 수료할 때 정말 잠깐동안 배운거라 머릿속에 남은게 없었다.

    트위터 클론코딩이 끌려서 무작정 시작했다.

    아리송했던 리액트 문법이 조금씩 정리되었다. 

    =

    임포트, 컴포넌트 하는것도 자바처럼 원하는 기능을 import하는거고 모듈화 해서 따로 관리하는것이라 생각되었다.

    마치 처음 mvc 패턴을 익혔을때가 생각난다. 

    그 전에는 무작정 한 소스에 다 때려넣었는데 이런식으로 하면 코드 덩치가 비대해지고 유지보수가 굉장히 어려워진다.

    실제로 ctrl F로 찾아다니다가 시간을 허비하고...

    독립적으로 관리할 수 있는 함수는 따로 관리하는 것이 좋다. 컴포넌트가 이와 같은 기능을 수행하는 것 같다.

    설치부터 에러나고 react-router-dom 버전이 달라지면서 레거시된 혹은 사라진 문법도 있었다.

    다시 책을 꼼꼼히 살펴보고 안되면 구글링을 해봤다. 

    나처럼 개정판 이후에 누이터 코딩을 하려다가 책과 달라진 리액트 버전에 고생한 사람들의 기록덕에 큰 문제는 없었다.

    =

    파이어베이스 스토리지를 연동하니 기본적인 회원가입/로그인/데이터 저장기능을 사용할 수 있어서 무척 좋았다.

    =

    습득한 지식을 다시 복습하고 주중에 시간이 나면 완성된 코드에서 다른 작업을 더 추가할까 싶다.

    실제 트위터는 글뿐만이 아니라 이미지 란이 따로 있어서 이미지만 모아서 볼 수있다. 이걸 구현해보고싶다.

     

     

    '공부 > 클론코딩' 카테고리의 다른 글

    Day 7 - 깃허브 작업  (0) 2024.09.12
    Day 7 - 누이터 서브 기능 만들기  (1) 2024.09.12
    Day 5 - 로그인 기능 구현하기 1  (0) 2024.09.09
    Day 4 - 클론 코딩 시작 2  (2) 2024.09.09
    Day 3 - 클론 코딩 시작  (0) 2024.09.07
    댓글