Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[chore] improve-render-efficiency #55

Merged
merged 1 commit into from
Nov 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 10 additions & 11 deletions fe/src/app/components/common/Navigation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import React from "react";
import { useRouter } from "next/navigation";
import Image from "next/image";

// Directly define NavigationProps in this file
interface NavigationProps {
showBackButton?: boolean;
title?: string;
Expand All @@ -19,29 +18,29 @@ function Navigation({
const router = useRouter();

const handleBackClick = () => {
if (onBack) {
onBack(); // Use the provided onBack function if available
} else {
router.back(); // Otherwise, use router.back()
}
if (onBack) onBack();
else router.back();
};

return (
<header className="fixed top-0 left-1/2 transform -translate-x-1/2 w-[360px] h-[56px] bg-white flex items-center justify-between px-4 z-10">
{showBackButton && (
<button type="button" onClick={handleBackClick} className="p-2">
<button
type="button"
onClick={handleBackClick}
className="p-2 flex items-center"
aria-label="뒤로가기"
>
<Image
src="/images/ArrowBack.svg"
alt="뒤로가기"
width={24}
height={24}
className="mr-2"
/>
</button>
)}
<h1
className="text-lg font-semibold text-[#2c2c2c] mx-auto"
style={{ width: "320px", textAlign: "center" }}
>
<h1 className="text-lg font-semibold text-[#2c2c2c] text-center w-full truncate">
{title}
</h1>
</header>
Expand Down
62 changes: 28 additions & 34 deletions fe/src/app/eventcreate-page/components/EventNameInput.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,4 @@
// components/EventNameInput.tsx

"use client";

import React, { useEffect, useState } from "react";
import React, { useEffect, useState, useCallback } from "react";
import Image from "next/image";
import { EventNameInputProps } from "@/app/eventcreate-page/types/types";

Expand All @@ -21,73 +17,71 @@ function EventNameInput({
}: EventNameInputProps) {
const [hasUserEdited, setHasUserEdited] = useState(false);
const currentDate = getCurrentDate();
const showWarning = hasUserEdited && value.trim().length < 1;

useEffect(() => {
if (!hasUserEdited && selectedLocation) {
const newEventName = `${currentDate} ${selectedLocation} 모임`;
if (!hasUserEdited) {
const newEventName = selectedLocation
? `${currentDate} ${selectedLocation} 모임`
: "";
onChange(newEventName);
} else if (!selectedLocation && !hasUserEdited) {
onChange("");
}
}, [selectedLocation, currentDate, onChange, hasUserEdited]);

const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const newValue = e.target.value;
setHasUserEdited(true);
onChange(newValue);
};
const handleInputChange = useCallback(
(e: React.ChangeEvent<HTMLInputElement>) => {
setHasUserEdited(true);
onChange(e.target.value);
},
[onChange]
);

const handleClear = () => {
const handleClear = useCallback(() => {
setHasUserEdited(true);
onChange("");
};

const borderClass = "border-transparent";
const showWarning = hasUserEdited && value.trim().length < 1;
}, [onChange]);

return (
<div className={`relative flex flex-col ${className} mt-4`}>
<div className="text-[#2c2c2c] text-lg font-semibold font-['Pretendard'] leading-relaxed mb-2">
<div className={`relative flex flex-col mt-4 ${className}`}>
<label className="text-[#2c2c2c] text-lg font-semibold leading-relaxed mb-2">
이벤트 이름
</div>
</label>

<div
className={`relative w-[328px] h-14 p-4 bg-[#f7f7f7] rounded-lg flex justify-between items-center border-2 ${
showWarning ? "border-danger-base" : borderClass
showWarning ? "border-danger-base" : "border-transparent"
}`}
>
<input
type="text"
value={value}
onChange={handleInputChange}
placeholder={`${currentDate} 모임`}
className="bg-transparent border-none grow shrink basis-0 text-[#2c2c2c] text-base font-medium font-['Pretendard'] leading-normal outline-none flex-1"
className="bg-transparent text-[#2c2c2c] text-base font-medium outline-none flex-grow"
aria-label="이벤트 이름 입력"
/>

{value && (
<div
role="button"
tabIndex={0}
<button
type="button" // 명시적으로 type 속성을 추가하여 오류 해결
onClick={handleClear}
onKeyDown={(e) => {
if (e.key === "Enter" || e.key === " ") handleClear();
}}
className="w-5 h-5 relative cursor-pointer"
className="w-5 h-5 cursor-pointer"
aria-label="이름 삭제"
>
<Image
src="/images/Cancel.svg"
alt="삭제 아이콘"
width={24}
height={24}
/>
</div>
</button>
)}
</div>

{showWarning && (
<div className="text-danger-base text-sm font-medium font-['Pretendard'] leading-tight mt-2">
<p className="text-danger-base text-sm font-medium mt-2">
모임명은 1자 이상 작성 가능해요
</div>
</p>
)}
</div>
);
Expand Down
24 changes: 7 additions & 17 deletions fe/src/app/eventcreate-page/components/LocationInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import React, { useEffect, useState } from "react";
import { useRouter } from "next/navigation";
import Image from "next/image";
import { useLocationStore } from "@/app/eventcreate-page/stores/useLocationStore";
import { LocationInputProps } from "@/app/eventcreate-page/types/types";
import { LocationInputProps } from "@/app/eventcreate-page/types/types"; // 'Place'를 삭제

function LocationInput({
className,
Expand All @@ -18,29 +18,19 @@ function LocationInput({
useEffect(() => {
if (selectedLocation && selectedLocation.name !== location) {
setLocationState(selectedLocation.name);
if (onSelect) {
onSelect(selectedLocation);
}
onSelect?.(selectedLocation);
}
}, [selectedLocation, onSelect, location]);

useEffect(() => {
setLocationState(value);
}, [value]);

const handleLocationInputClick = () => {
router.push("/eventcreate-page/location-search");
};

return (
<div className={`relative flex flex-col ${className} mt-4`}>
<div className="text-[#2c2c2c] text-xl font-semibold font-['Pretendard'] leading-loose mb-4">
<label className="text-[#2c2c2c] text-xl font-semibold font-['Pretendard'] leading-loose mb-4">
어떤 공간을 찾고 계신가요?
</div>
</label>
<button
type="button"
className="relative w-[328px] h-14 px-4 bg-[#f7f7f7] rounded-lg flex items-center mb-4 cursor-pointer"
onClick={handleLocationInputClick}
className="relative w-[328px] h-14 px-4 bg-[#f7f7f7] rounded-lg flex items-center cursor-pointer"
onClick={() => router.push("/eventcreate-page/location-search")}
>
<Image
src="/images/Search.svg"
Expand All @@ -53,7 +43,7 @@ function LocationInput({
type="text"
value={location}
placeholder="장소를 입력해주세요"
className="bg-transparent border-none flex-grow text-[#2c2c2c] text-base font-medium font-['Pretendard'] leading-normal outline-none"
className="bg-transparent border-none flex-grow text-[#2c2c2c] text-base font-medium outline-none"
readOnly
/>
</button>
Expand Down
40 changes: 15 additions & 25 deletions fe/src/app/eventcreate-page/components/SearchResults.tsx
Original file line number Diff line number Diff line change
@@ -1,34 +1,24 @@
// components/SearchResults.tsx

import React from "react";
import React, { useMemo } from "react";
import SearchResultItem from "@/app/eventcreate-page/components/SearchResultsItem";

interface Place {
name: string;
address: string;
px?: number;
py?: number;
}

interface SearchResultsProps {
results: Place[];
searchTerm: string;
onSelect: (place: Place) => void;
}
import { SearchResultsProps } from "@/app/eventcreate-page/types/types";

function SearchResults({ results, searchTerm, onSelect }: SearchResultsProps) {
if (!results || results.length === 0) return null;
const renderedResults = useMemo(() => {
return results.map((place) => (
<SearchResultItem
key={place.name}
place={place}
searchTerm={searchTerm}
onClick={() => onSelect(place)}
/>
));
}, [results, searchTerm, onSelect]);

if (results.length === 0) return null;

return (
<ul className="w-full max-w-[360px] max-h-[240px] overflow-y-auto bg-white mt-2">
{results.map((place) => (
<SearchResultItem
key={place.name}
place={place}
searchTerm={searchTerm}
onClick={() => onSelect(place)}
/>
))}
{renderedResults}
</ul>
);
}
Expand Down
71 changes: 39 additions & 32 deletions fe/src/app/eventcreate-page/components/SearchResultsItem.tsx
Original file line number Diff line number Diff line change
@@ -1,53 +1,60 @@
import React from "react";
import React, { useMemo, useCallback } from "react";
import Image from "next/image";

interface SearchResultItemProps {
place: { name: string; address: string };
searchTerm: string;
onClick: () => void;
}
import { SearchResultItemProps } from "@/app/eventcreate-page/types/types";

function SearchResultItem({
place,
searchTerm,
onClick,
}: SearchResultItemProps) {
// Function to highlight matching text in blue
const highlightText = (text: string, highlight: string) => {
if (!highlight.trim()) return text;
// useMemo와 useCallback 훅을 최상위에서 호출
const highlightText = useCallback(
(text: string, highlight: string) => {
if (!highlight.trim()) return text;

const regex = new RegExp(`(${highlight})`, "gi");
const parts = text.split(regex);
const regex = new RegExp(`(${highlight})`, "gi");
const parts = text.split(regex);

return (
<>
{parts.map((part, index) => {
const isHighlighted = part.toLowerCase() === highlight.toLowerCase();
return (
return (
<>
{parts.map((part) => (
<span
key={
isHighlighted
? `highlight-${part}-${index}`
: `text-${part}-${index}`
key={`${place.id}-${part}`} // 고유한 place.id와 part 조합
style={{
color:
part.toLowerCase() === highlight.toLowerCase()
? "#3a91ea"
: "#4a4a4a",
}}
className={
part.toLowerCase() === highlight.toLowerCase()
? "font-semibold"
: "text-gray-800"
}
style={
isHighlighted ? { color: "#3a91ea" } : { color: "#4a4a4a" }
}
className={isHighlighted ? "font-semibold" : "text-gray-800"}
>
{part}
</span>
);
})}
</>
);
};
))}
</>
);
},
[place.id]
);

const highlightedText = useMemo(
() => highlightText(place.name, searchTerm),
[place.name, searchTerm, highlightText]
);

// 훅 호출 이후에 조건부 렌더링을 수행
if (!place.id) return null;

return (
<button
type="button"
onClick={onClick}
className="w-full h-11 px-4 py-2.5 bg-white flex items-center gap-2 hover:bg-gray-100 transition-colors duration-200"
aria-label={`Select ${place.name}`}
>
<div className="w-6 h-6 flex-shrink-0">
<Image
Expand All @@ -57,8 +64,8 @@ function SearchResultItem({
height={24}
/>
</div>
<div className="text-base font-medium font-['Pretendard'] text-gray-800 leading-none truncate">
{highlightText(place.name, searchTerm)}
<div className="text-base font-medium font-['Pretendard'] text-gray-800 leading-none truncate pt-1">
{highlightedText}
</div>
</button>
);
Expand Down
Loading
Loading