반응형

FrontEnd에서 Axios와 Promise.all()을 같이 사용하는 방법입니다.

서버와 통신하는 여러가지 방법중 한가지입니다.

 

먼저 가장 단순하게 사용하는 방법입니다.

./api.ts

import axios from "axios";

const baseUrl = "http://localhost:4000/api";

const api = axios.create({
    baseURL: baseUrl,
    headers: {}
});

export { api };

 

app.tsx

import { api } from "@/services/api";
import { useEffect, useState } from "react";

const App = () => {
    useEffect(() => {
        api.post("/main/find")
        .then(res => {
            console.log("OK");
        })
        .catch(err => console.log("Main Find Err", err));

        api.post("/sub/find")
        .then(res => {
            console.log("OK");
        })
        .catch(err => console.log("Sub Find Err", err));
    }, [])

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

export default App;

위 내용이 가장 기본적인 방법이라고 생각합니다.

 

promise.all()과 함께 사용하기

const App = () => {
    useEffect(() => {

        const promises = [
            api.post("/main/find"),
            api.post("/sub/find")
        ];

        Promise.all(promises)
        .then(([mainRes, subRes]) => {
            if (mainRes.data.code === "y") console.log("Main Res OK");

            if (subRes.data.code === "y") console.log("Sub Res OK");
        })
        .catch(err => console.log("Err Res", err));
    }, [])

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

export default App;

이런식으로 promise.all()을 사용하면 브라우저와 서버 간의 왕복 횟수가 줄어들어 성능이 향상되고 리소스를 효율적으로 사용할 수 있습니다.

 

:: 물론 이런방법 말고도 서버쪽에서 해결 한다음 앞으로 보내주는 방법들도 존재합니다.

반응형
반응형

잘못된 href가 라우터에 전달되었습니다.

Invalid href '/search/main//' passed to next/router in page: '/search/[...id]'. Repeated forward-slashes (//) or backslashes \ are not valid in the href.

 

next.js .tsx에서 링크를 사용할 때 문제가 발생했습니다.

<Link href={`/search/main/${options.filter}/${options.search}`}>Move</Link> 이런식으로 링크를 걸어두었는데, options가 불러와지기 전에 .tsx가 실행되면서 위와같은 에러를 불러온것 같습니다.

 

.tsx

import ...

const App = () => {
    
    const [options, setOptions] = useState({
        type: "",
        filter: "",
        search: ""
    })
    
    useEffect(() => {
        if (router.query.id) {
            setOptions({
                type: router.query.id[0],
                filter: router.query.id[1],
                search: router.query.id[2]
            })
        }
    }, [router.query.id]);
    
    return (
        <>
            <Link href={`/search/main/${options.filter}/${options.search}`}>메인</Link>
        </>
    );
}

export default App;

위처럼 options에 내용이 들어오기 전 .tsx가 실행되었기 때문에 내용이 들어가지 않아서 '/search/main//'와 같이 에러가 생겨났던 거였습니다.

 

해결방안

return (
    options.type !== "" && <>
        <Link href={`/search/main/${options.filter}/${options.search}`}>메인</Link>
    </>
);

이런식으로 options의 내용을 가져오기 전에 불러오지 않도록 설정해주었습니다.

반응형
반응형

Uncaught DOMException: Failed to execute 'removeChild' on 'Node': The node to be removed is not a child of this node.

next.js에서 내용을 불러오고 제거하는 과정에서 오류가 발생했습니다.



App.jsx

...


const App = () => {
    
    const [test, setTest] = useState([]);
    
    useEffect(() => {
        setTest([
            {hello: 1},
            {hello2: 2},
            {hello3: 3},
        ])
    }, [])
    return (
        <div>
            {
                test.length > 0 && test.map(item => (
                    <div></div>
                ))
            }
        </div>
    )
}

export default App;

예를 들어, 위와 같은 내용이 있을 때 안에 내용을 지워줄 때 제대로 지워지지 않았습니다.

내용이 완전히 삭제되지 않고 [{"": 0}] 이런 식으로 데이터가 남아버리는 상태가 되었는데 데이터가 제대로 지워지지 않았기에 <div></div> 이곳에서 에러가 발생하게 됐습니다.

( 실제 코드에서는 useMemo에서 test내용이 바뀔때마다 실행이 되는 바람에 [{"": 0}] 이런 식으로 배열이 생기게 됐었습니다. )

 

그래서 test 내용을 깨끗이 비워줄 수 있도록 처리를 하고 해결을 했습니다.

( useMemo에서 불러올 내용이 없으면 setTest([])를 만들어 줘서 해결했습니다. )

반응형
반응형

next.js에서 useRouter를 사용해 url이동과 함께 데이터를 전달하는 방법입니다.

ex ) localhost:3000 --> localhost:3000/hello URL로 이동할 때 데이터를 함께 가져가는 방법입니다.

 

index.tsx

import { useRouter } from "next/router";

const Index = () => {
    
    const router = useRouter();
    
    const move = () => {
    const data = { contents: {
        id: shiro,
        pass: 1234,
        nick: jnu
    } };
        router.push({
            pathname: "/hello",
            query: { data: JSON.stringify(data) }
            as={`/hello`}
        });
    }
    
    return (
        <button onClick={move}>이동</button>
    )
}

export default Index;

query에 데이터를 담아주고, as부분을 활용해 주소창에 query 내용이 나오는걸 방지해줍니다.

 

 

hello.tsx

import { useRouter } from "next/router";

const Hello = () => {
    
    const router = useRouter();
    
    const { data } = router.query;
    const parsedData = data ? JSON.parse(data as string) : null;
    
    console.log(parsedData);
    
    return (
        ...
    )
}

export default Hello;

 

반응형
반응형

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

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내부 내용을 변경할 수 있습니다.

반응형

+ Recent posts