반응형

부모 컴포넌트에서 자식 컴포넌트로 데이터를 전달하는 방법과 자식 컴포넌트에서 부모 컴포넌트로 데이터를 전달하는 방법입니다.

ReactJS, NextJS에서 props를 사용하여 간단하게 데이터를 전달하는 방법입니다.

요즘 자꾸 깜빡깜빡해서 적어둔 내용입니다.

 

1. 부모 컴포넌트에서 자식 컴포넌트로 데이터 전달하기

Parent Component --> Child Component

 

Parent.tsx

import ...

const Parent = () => {
    
    const [data, setData] = useState<string>("문자열");

    return (
        <>
            <Child data={data}></Child>
        </>
    );
}

export default Parent;

 

Child.tsx

import ...

const Child = ({ data }: { data: string}) => {
    
    console.log(data);

    return (
        <>
            <div>
                Child Page
            </div>
        </>
    );
}

export default Child;

 

2. 자식 컴포넌트에서 부모 컴포넌트로 데이터 전달하기

Child Component --> Parent Component

 

Parent.tsx

const Parent = () => {
    
    const parentFunc = (str: string) => console.log(str);

    return (
        <>
            <Child parentFunc={parentFunc}></Child>
        </>
    );
}

export default Parent;

 

Child.tsx

const Child = ({ parentFunc }: { parentFunc: Function }) => {
    
    const [data, setData] = useState("Hello World");

    parentFunc(data);

    return (
        <>
            <div>Child</div>
        </>
    );
}

export default Child;

 

반응형
반응형

웹사이트에서 글 작성중일 때 뒤로 가기 및 새로고침 방지하기

웹사이트에서 글을 작성중일때, 가끔 뒤로 가기를 누르거나 새로고침을 누르는 경우가 종종 있습니다.

이때, 이를 방지하는 방법을 적용시켜야 하는 상황이 생겨서 적어두는 내용입니다.

 

const App = () => {
    
    const router = useRouter();

    useEffect(() => {
        // 크롬에서 새로고침을 누를 때 발생하는 내용입니다.
        const handleBeforeUnload = (e: BeforeUnloadEvent) => {
            e.preventDefault();
            e.returnValue = "";
        }

        const handleRouteChangeStart = (url: string) => {
            const shouldLeave = confirm("정말 페이지를 떠나시겠습니까?\n내용은 저장되지 않습니다.");
            if (!shouldLeave) {
                router.events.emit("routeChangeError");
                throw "routeChange Aborted";
            }
        }

        window.addEventListener("beforeunload", handleBeforeUnload);
        router.events.on("routeChangeStart", handleRouteChangeStart);

        return () => {
            window.removeEventListener("beforeunload", handleBeforeUnload);
            router.events.off("routeChangeStart", handleRouteChangeStart);
        }
    }, [router]);

    return (
        <>
            <div>Pages...</div>
        </>
    );
}

export default App;

사실 SweetAlert2를 사용해서 적용을 해보고 싶었는데, async await으로 동작이 될 줄 알았는데 뒤로 가기가 강제로 실행됐습니다..ㅠ

좀 더 알아보고 가능한 방향을 찾아봐야 할 듯합니다.

반응형
반응형

input을 사용하여 같은 파일 연속으로 올리기

input을 사용해서 파일을 올릴때 연속으로 같은 파일을 올리는 경우나, 파일을 올리고 삭제한다음 다시 올리는 경우가 있습니다.

하지만 기본적으로 같은 파일을 다시 업로드 할 경우에는 이벤트가 트리거 되지 않습니다.

그렇기 때문에 파일을 올려준 이후에 value값을 초기화 시켜 주어야합니다.

 

input type="file" value 초기화 시켜주기

import ...

const App = () => {
    const [image, setImage] = useState("");
    
    const imageUpload = (e: ChangeEvent<HTMLInputElement>) => {
        const { target: { files } } = e;
        
        const (files as FileList);
        
        if (file === undefined) return;
        
        reader.onloadend = () => {
            setImage(String(reader.result)):
            
            e.target.value = "";
        }
        
        reader.readAsDataURL(file);
    }
    
    return (
        <input type="file" accept="image/*" onChange={(e) => imageUpload(e)} />
    );
}

e.target.value = "";로 초기화해주면 동일한 파일을 다시 올려도 문제없이 올라갑니다.

반응형
반응형

image를 추가할때 multiple기능을 사용하여 여러 이미지를 추가할 때, File과 미리보기를 담는 방법입니다.

 

import ...

interface dataProps {
    images: string[],
    imagesFile: File[]
}

const App = () => {

    // 메인 이미지 컨텐츠
    const [data, setData] = useState<dataProps>({
        images: [],
        imagesFile: []
    })
    const contentsImage = async (e: ChangeEvent<HTMLInputElement>) => {
        const { target: { files }, currentTarget } = e;

        let file = (files as FileList);

        if (file === undefined) return;

        let newImageList: File[] = [];
        let newImageListPreview: string[] = [];

        for (let i = 0; i < file.length; i++) {
            try {
                const dataUrl = await readAsData(file[i]);
                newImageListPreview.push(dataUrl);
                newImageList.push(file[i]);
            } catch (err) {
                console.log(err);
            }
        }

        setData({...data, images: [...data.images, ...newImageListPreview], imagesFile: [...data.imagesFile, ...newImageList]})
    }
    const readAsData = (file: File): Promise<string> => new Promise((resolve, reject) => {
        const reader = new FileReader();

        reader.onload = () => {
            resolve(reader.result as string);
        }
        reader.onerror = (error) => {
            reject(error);
        }
        reader.readAsDataURL(file);
    })
    return (
        <>
            <input type="file" accept="image/*" onChange={(e) => contentsImage(e)} multiple />
        </>
    );
}

export default App;

이 후, 내용에 확장자 검사, 용량 검사 등등을 추가시켜 주었습니다.

반응형
반응형

태그를 만드는 도중 onKeyPress는 이제 사용되지 않는다는 말을 보고 어떻게 사용해야 할까 고민하다 사용한 방법입니다.

 

태그를 생성할 때, onKeyDown, onKeyUp을 둘 다 사용해 봤지만 일반적인 방법으로는 연속적인 클릭으로 인해 alert창을 띄워도 바로 사라지는 현상이 있었습니다.

onKeyDown은 살짝 누르는 순간에도 연속적으로 내용이 들어가고, onKeyUp은 누를 때 한번 뗄 때 한번 클릭이 되는 바람에 고민하게 된 내용입니다.

 

1. onBeforeInput

깔끔하게 숫자, 영문, 한글은 입력이 되지만 정작 중요한 엔터키가 먹히지 않았습니다.....

 

2. onKeyDown에 preventDefault(); 추가하기

onKeyDown을 사용하면서 가장 윗줄에 이벤트의 기본 동작을 방지해주는 preventDefault()를 추가해 줬습니다.

 

app.tsx

const tagKeyCode = (e: KeyboardEvent<HTMLInputElement>) => {
    const { code: key } = e;
    
    if (key === "Enter") {
        e.preventDefault();
        
        ...
    }
}

위처럼 Enter키를 눌렀을 때, 기본 동작을 방지한 다음 제가 원하는 기능을 집어넣는 방법으로 진행하였습니다.

 

 

:: 그렇다고 onKeyPress를 사용하지 말아야 하는 것은 아닌 것 같습니다. onKeyPress에 경고문구가 뜨기는 하지만, 단지 경고일 뿐이고 사용할 때 뭔가 오류가 난다거나 실서버에서 문제가 생긴다거나 하는 부분은 없었습니다.

 

반응형
반응형

useState를 사용할 때, Object내부에 오브젝트를 변경하는 방법입니다.

항상 useState를 사용할 때, Object는 사용했었지만 Object 안에 Object를 변경하는 방법이 순간 헷갈렸었기에 작성하는 글입니다.

 

예시

import ...

const App = () => {

    const [user, setUser] = useState({
        nickname: "HelloWorld",
        userData: {
            ability: "댄스",
            description: "안녕하세요..."
        }
    });
    return (
        <>
            <input type="text"
                value={user.nickname}
                onChange={(e: ChangeEvent<HTMLInputElement>) => setUser({ ...user, nickname: e.target.value})}
            />
        </>
    );
}

export default App;

위처럼 user의 닉네임을 바꾸기 위해서는 스프레드를 사용해서 간단하게 바꿀수가 있습니다.

그런데 user안의 userData내부 내용을 바꾸기 위해서는 한번 더 안으로 들어가야 합니다.

 

userData의 ability 변경하기

import ...

const App = () => {

    const [user, setUser] = useState({
        nickname: "HelloWorld",
        userData: {
            ability: "댄스",
            description: "안녕하세요..."
        }
    });
    return (
        <>
            <input type="text"
                value={user.userData.ability}
                onChange={(e: ChangeEvent<HTMLInputElement>) => setUser({ ...user, userData: { ...user.userData, ability: e.target.value}})}
            />
        </>
    );
}

export default App;

위처럼 접근하면 user안의 userData내부 내용을 변경할 수 있습니다.

반응형
반응형

Image is missing required "src" property: 에러에 관한 내용입니다.

이미지를 넣는 과정에서 이런 에러가 나오는 경우가 종종 있습니다.

Image src부분이나 alt부분에 제대로된 값이 들어가기전에 한번 실행되기 때문인것으로 생각됩니다.

 

import ...

const App = ({ user }, { user: UserProps }) => {
    
    return (
        <Image width={9999} height={9999} src={user.profile} alt={userWrap.user.nickname + "님의 배경 프로필"} />
    );
}

위와 같은 내용이 있을 때, user에 제대로된 내용이 들어오기전에 undefined가 들어가서 실행을 한번 해줘서 일어나는 현상이라 생각됩니다.

 

해결방법

import ...

const App = ({ user }, { user: UserProps }) => {
    
    return (
        { user.profile && <Image width={9999} height={9999} src={user.profile} alt={userWrap.user.nickname + "님의 배경 프로필"} /> }
    );
}

다른 방법도 있을지 모르지만, "user.profile에 내용이 담기면 실행해라" 라고 일단은 설정을 해두었습니다. 

반응형
반응형

클라이언트 쪽에서 httpOnly cookie내용을 가져오는 방법입니다.

이번에 클라이언트 부분에서 쿠키값을 가져와야 하는 상황이 생겨서 쿠키값을 가져오려 하는데, httpOnly 때문인지는 몰라도 js-cookie, cookies-next 등등을 사용해 봐도 빈 토큰값만 가져오는 상황이 발생했습니다.

:: getServerSideProps는 사용하지 않았습니다.

 

useEffect 훅을 사용해서 pages/api/ 내부에 credentials.ts를 생성하여 서버에서 쿠키내용을 가져오게끔 만들었습니다.

 

pages/api/credentials.ts

import { NextApiRequest, NextApiResponse } from "next";
import cookie from "cookie";

export default function handler(req: NextApiRequest, res: NextApiResponse) {

    const cookies = cookie.parse(req.headers.cookie || "");
    const cookieValue = cookies["cookie-name"];

    res.status(200).json({ cookieValue });
}

 

app.tsx

import ...

const App = () => {

    useEffect(() => {
        const fetchCookieValue = async () => {
            const response = await fetch("/api/credentials", { credentials: "include" });
            const data = await response.json();

            if (data.cookieValue === undefined) {
                // 토큰이 아예 없는 경우
                Swal.fire({
                    title: '로그인',
                    text: "로그인 하러 가시겠습니까?",
                    icon: 'warning',
                    showCancelButton: true,
                    confirmButtonColor: '#3085d6',
                    cancelButtonColor: '#d33',
                    confirmButtonText: '로그인 하러가기',
                    cancelButtonText: "취소"
                })
                .then((result) => {
                    if (result.isConfirmed) router.push("/login");
                    else router.push("/");
                })
            } else {

                await setTokenCookie(data.cookieValue);

                const token: any | null = jwt.decode(data.cookieValue, { complete: true });

                dispatch(userList(token.payload.user[0]));

            }

        };

        fetchCookieValue();
    }, []);
    
    
    return (...);
}
반응형
반응형

vercel에 간단하게 만든 사이트 빌드하기 입니다.

이전 작성했던 gh-pages를 이용해서 웹 페이지 호스팅하기와 비슷한 내용입니다.

gh-pages를 이용한 웹 페이지 호스팅하기 바로가기

Vercel 바로가기

 

1. github에 ReactJS or NextJS로 작성된 파일을 올렸다고 가정한 이후에 시작합니다.

 

2. vercel site로 이동합니다.

 

3. 아이디를 생성하고, 깃허브를 연동시켜줍니다.

 

4. dashboard에서 Add New...를 클릭하고 Project로 이동합니다.

 

4. 자신이 생성한 NextJS파일을 Import해줍니다.

 

5. Environment Variables에 env에 적어준 내용을 적어준다고 생각하면 편합니다. 이후 Deploy를 눌러주면 끝납니다.

( env파일을 github에 직접 올리지 않기 때문에 여기에 env내용을 적어준다고 생각하시면 됩니다. )

 

6. 폭죽이 터지면서 성공했다는 내용이 나옵니다. 만약 실패하면 실패한 내용들이 아래 주루룩 나오게 됩니다.

 

이후 업데이트를 하려면 github에 내용을 업데이트 해주면 자동으로 vercel에서도 업데이트를 해줍니다.

반응형
반응형

getStaticProps VS getServerSideProps

 

getStaticProps ( SSG: Static Site Generation )

getStaticProps는 최초 빌드 시에 딱 한번만 호출이 됩니다. 즉, 최초 빌드 시 빌드되는 값이 추후에 수정되는 일이 없는 경우에 사용하기 좋습니다. 

장점은 호출 시 마다 매번 fetch를 하지 않기 때문에 성능면에서는 getServerSideProps보다 좋습니다.

 

export async function getStaticProps() {

    return {
        props: {}
    }
}

 

getServerSideProps ( SSR: Server Side Rendering )

getServerSideProps는 getStaticProps와 다르게 요청이 들어올 때마다 호출되며, 그 때마다 사전 렌더링을 진행합니다.

이 경우, 요청 시마다 다시 호출하기 때문에 빌드 이후 자주 바뀌게 될 동적 데이터가 들어갈 때 사용하기 좋습니다.

 

export async function getServerSideProps() {

    return {
        props: {}
    }
}

getServerSideProps는 서버와 관련된 기능입니다.

getServerSideProps()안에 들어가는 코드는 어떤 코드를 쓰던지 서버에서 작동합니다.

이걸 이용해서 API Key를 숨기는것도 가능합니다. ( BackEnd에서 실행되기 때문입니다. )

 

호출은 _app.js의 component를 호출하고 pageProps에서 호출된다고 생각하면 됩니다.

 

Only absolute URL

getServerSideProps는 서버에서 작동하기 때문에 프론트엔드에서 실행할 때와 다르게 URL이 없기 때문에 fetch를 사용할 때 절대주소를 입력해주어야 합니다.

export async function getServerSideProps() {

    // TypeError: Only absolute URLs are supported
    const { results } = await ( await fetch(`http://localhost:3000/~`)).json();

    return {
        // props key 내부에는 원하는 데이터를 아무거나 넣을 수 있다. ( 무엇을 return하던지 props로써 page에게 주게된다. ex) Home())
        // 이 데이터는 pageProps를 통해 전달된다.
        props: {}
    }
}

 

반응형

+ Recent posts