본문 바로가기
제니의 개발일지/개발일지

[React] 새 창 HTML 미리보기, HTML 데이터를 로드하고 창이 닫힐 때 데이터를 제거하는 로직 구현하기

by 제니운 2024. 6. 14.
728x90

 

 

 

 

안녕하세요. 제니입니다!

오늘은 새 창 html 미리보기 구현을 한 내용을 정리해보려고 하는데요.

쉽게 생각해서, 제목, 내용을 입력하고 이메일을 보내는 양식이 있다고 가정할 때,

이런 경우 프론트에서 발송하는게 아니라 서버측에서 발송을 하게 됩니다.

백엔드 개발자가 html 양식을 가지고 있을거고

그 양식을 받아 미리보기로 보여주는 화면을 만들어야 하는 상황이 발생되어서 정리하게 되었습니다.

 

 

 

원하는 결과는

새 창에서 HTML 데이터를 로드하고 창이 닫힐 때, 데이터를 제거하는 로직을 구현하는 것이었습니다!

 

 

 

1. response 는 html을 string 형태로 반환

api를 반환하는 걸 html 파일로 받게 되면 다운로드가 되므로,

백에서 response 값을 html 그대로 넘겨주면 됩니다.

 

 

 

2. get이 아닌 post

미리보기 형태이기 때문에 body에 title, content등을 보내야 하므로 get이 아닌 post 형태를 사용해야 합니다.

get의 경우에 body를 보내게 되면 window에서 거부할 수 있어, post로 반환하는 것이 가장 쉬운 방법이었습니다.

 

 

 

import { RETURN_TYPE } from '@/constant/type';
import { API_SERVER } from '@/utils/constant';

interface postEmailPreviewProps {
	accessToken: string;
	title: string;
	content: string;
}

const postEmailPreview = async ({ accessToken, title, content }: postEmailPreviewProps): Promise<RETURN_TYPE> => {
	const response = await fetch(`${API_SERVER}/email/preview`, {
		method: 'POST',
		headers: {
			'Content-Type': 'application/json',
			Authorization: `Bearer ${accessToken}`,
		},
		body: JSON.stringify({
			title,
			content,
		}),
	});
	if (response.ok) {
		const data = await response.text();
		return {
			status: response.status,
			message: data,
		};
	} else {
		const errorData = await response.json();
		return {
			status: response.status,
			message: errorData.message,
			errorType: errorData.type,
		};
	}
};

export default postEmailPreview;

 

 

 

 

예를 들어 이러한 fetch 함수를 만들어 두게 된다고 하면,

return message 값을 바로 받아서 처리를 하게 되는 건데요.

 

 

 

3. 미리보기 버튼 함수

const onClickPreviewBtn = useCallback(async () => {
    try {
        if (token) {
            const { status, message, errorType } = await postEmailPreview({
                accessToken: token,
                title: title,
                content: content.desc,
            });
            if (status === 200) {
                const previewWindow = window.open(`${managerPath.MAIL_PREVIEW}`, '_blank', 'width=640, height=640');
                if (previewWindow) {
                    previewWindow.onload = () => {
                        previewWindow.localStorage.setItem(LOCAL_STORAGE_KEY.EMAIL_PREVIEW, message);
                    };
                    const checkWindowClosed = setInterval(() => {
                        if (previewWindow.closed) {
                            clearInterval(checkWindowClosed);
                            localStorage.removeItem(LOCAL_STORAGE_KEY.EMAIL_PREVIEW);
                        }
                    }, 0);
                } else {
                    alert('팝업 차단이 설정되어 있습니다. 팝업 차단을 해제하고 다시 시도해주세요.');
                }
            } else if (errorType === 'token') {
                openModal(<ErrorModal title={message} onClose={isNavigateLogin} />);
            } else {
                openModal(<ErrorModal title={message} />);
            }
        }
    } catch (error) {
        openModal(<ErrorModal />);
    }
}, [content.desc, isNavigateLogin, title, token]);

 

 

 

예시로 미리보기 버튼 함수를 정리해보면 이와 같습니다.

위 함수 내용을 정리해볼게요.

 

 

 

3-1. 새 창열기

위 함수 내에 코드를 분석해보면,

 

 

 

	const previewWindow = window.open(`${managerPath.MAIL_PREVIEW}`, '_blank', 'width=640, height=640');

 

 

 

이런식으로 진행을 하게 된 건데, path 경로를 적어주고 원하는 width, height를 지정해주었습니다.

경로는 미리 라우터에 만들어두시면 돼요!

 

 

 

3-2. 새 창 팝업 에러가 있을 경우

const previewWindow = window.open(`${managerPath.MAIL_PREVIEW}`, '_blank', 'width=640, height=640');
if (previewWindow) {
 //
} else {
    alert('팝업 차단이 설정되어 있습니다. 팝업 차단을 해제하고 다시 시도해주세요.');
}

 

 

 

새 창을 여는데에 팝업 차단이 있을 수 있으므로, 

previewWindow가 없을 때 alert을 띄우도록 해주었어요!

 

 

 

3-3. api 반환받은 html 데이터는 localStorage 저장하기

왜 localStorage 저장을 한 걸까요?

보통 저는 redux store를 사용하는데요!

이 경우엔, 새 탭을 열거나 모달이 아닌, 새 창을 열기 때문입니다.

새 창을 열기 때문에 store에 저장한 데이터는 소실이 될 수 있고 새로고침을 하면 날라가기 때문에

localStorage에 저장을 해줬습니다.

 

 

 

const previewWindow = window.open(`${managerPath.MAIL_PREVIEW}`, '_blank', 'width=640, height=640');
if (previewWindow) {
    previewWindow.onload = () => {
        previewWindow.localStorage.setItem(LOCAL_STORAGE_KEY.EMAIL_PREVIEW, message);
    };
} else {
    alert('팝업 차단이 설정되어 있습니다. 팝업 차단을 해제하고 다시 시도해주세요.');
}

 

 

 

보시면 previewWindow가 onload 될때 setItem 해주신 코드를 확인하실 수 있어요!

저는 constans 파일에 LOCAL_STORAGE_KEY를 저장하고 있어서 저렇게 작성해뒀고

api에서 반환받은 message를 저장해줬습니다!

 

 

 

4. html 읽는 페이지

import ReadEditor from '@/components/common/editor/read-editor';
import { LOCAL_STORAGE_KEY } from '@/utils/constant';
import React, { useEffect, useState } from 'react';

const ManagerMailPreview = () => {
	const [readHtml, setReadHtml] = useState('');

	useEffect(() => {
		const emailPreviewHtml = window.localStorage.getItem(LOCAL_STORAGE_KEY.EMAIL_PREVIEW);
		if (emailPreviewHtml) {
			setReadHtml(emailPreviewHtml);
		}
	}, []);

	return (
		<div>
			<div dangerouslySetInnerHTML={{ __html: readHtml }} />
		</div>
	);
};

export default ManagerMailPreview;

 

 

 

전체 코드는 이렇습니다.

dangerouslySetInnerHTML을 사용해서 html 을 바로 보여줄 수 있어,

이러한 것은 굉장히 쉽게 적용할 수 있는데요.

 

 

 

4-1. localStorage 데이터 가져오기

const [readHtml, setReadHtml] = useState<string>('');

useEffect(() => {
    const emailPreviewHtml = window.localStorage.getItem(LOCAL_STORAGE_KEY.EMAIL_PREVIEW);
    if (emailPreviewHtml) {
        setReadHtml(emailPreviewHtml);
    }
}, []);

 

 

 

useEffect로 localStorage에서 getItem을 해 저장해둔 메시지를 가지고 왔고

setState를 해준 코드를 확인하실 수 있습니다.

 

 

 

5. localStorage 데이터 삭제하기

해당 부분이 가장 중요하게 생각한 부분인데요.

return 하자마자 localStorage 데이터를 삭제해버리면, 새로고침하게 됐을 때

미리보기 한 데이터가 빈 창으로 바뀌므로 사용자의 입장에서 좋지 않을 것 같았습니다.

그래서 고민을 했는데,

그럼 새 창을 끌 때, localStorage의 데이터를 삭제하면 되지 않을까?

라고 생각하게 되었어요.

 

 

 

Q. 그럼 새 창을 여는 컴포넌트에서 조작해야하는가?

일반적으로는 그러한데, 새 창의 경우에는 그렇지 않았습니다.

새 창으로 열어두었기 때문에 어떻게 제어를 걸어도 새로고침하면 데이터가 사라졌어요.

 

 

 

그래서 저는, 부모에서 새 창이 꺼졌을 때를 제어하는 방법으로 진행했습니다.

위에서 onClickPreviewBtn 즉, 미리보기 버튼 함수를 작성해뒀었는데요!

최종적으로

 

 

 

const previewWindow = window.open(`${managerPath.MAIL_PREVIEW}`, '_blank', 'width=640, height=640');
if (previewWindow) {
    previewWindow.onload = () => {
        previewWindow.localStorage.setItem(LOCAL_STORAGE_KEY.EMAIL_PREVIEW, message);
    };
    const checkWindowClosed = setInterval(() => {
        if (previewWindow.closed) {
            clearInterval(checkWindowClosed);
            localStorage.removeItem(LOCAL_STORAGE_KEY.EMAIL_PREVIEW);
        }
    }, 0);
} else {
    alert('팝업 차단이 설정되어 있습니다. 팝업 차단을 해제하고 다시 시도해주세요.');
}

 

 

 

checkWindowClosed라는 내용이 추가 된 부분을 확인해주세요.

만약 previewWindow인 새 창이 닫힐 경우, localStorage의 데이터를 삭제해주는 로직을 추가했습니다.

이렇게 되면, 새 창을 끌 때 바로 localStorage가 삭제되는 것을 확인하실 수 있어요!

 

 

 

오늘의 정리 끝!

 

728x90