본문 바로가기
개발개발/LifeGraph_인생그래프

[인생 그래프] 이모지 피커를 추가하자

by yelimu 2025. 2. 11.

내가 개발하면서 테스트하는 입장에서도 점을 찍고 거기에 타이틀을 적는게 손이 안간다고해야하나, 자연스럽지 않은 흐름인듯했다.

그래서 간단하게 이모지를 선택할 수 있으면 좋겠다 싶어서 emoji-picker-react 라이브러리를 이용했다.

https://www.npmjs.com/package/emoji-picker-react#hiddenemojis---excluding-certain-emojis

 

emoji-picker-react

Emoji Picker component for React Applications on the web. Latest version: 4.12.0, last published: 5 months ago. Start using emoji-picker-react in your project by running `npm i emoji-picker-react`. There are 284 other projects in the npm registry using emo

www.npmjs.com

설명이 나름 잘 되어있는거같은데, onEmojiClick 함수 타입 정의하는데서 좀 애먹었다.


문제점

1. 페이지 로드 시 이모지 피커가 열려있음

showPicker 값은 false임

2. 이모지를 선택해도 setTitle이 되지않음

 

전체 코드

 

import { ChangeEvent, MouseEvent as ReactMouseEvent, useEffect, useRef, useState } from "react";
import Point from "../Point/Point";
import { useGraphStore } from "../../../../store/useGraphStore";
import GraphContainer from "./GraphContainer";
import EmojiPicker, { EmojiClickData } from "emoji-picker-react";
import { MouseDownEvent } from "emoji-picker-react/dist/config/config";

const Graph = () => {
  const { points, addPoint, setTitle } = useGraphStore();
  const inputRef = useRef<HTMLInputElement>(null);
  const [showPicker, setShowPicker] = useState(false);

  const handleClick = (e: ReactMouseEvent<HTMLDivElement>) => {
    const rect = e.currentTarget.getBoundingClientRect();
    const x = e.clientX - rect.left;
    const y = e.clientY - rect.top;
    addPoint({ id: Date.now(), x, y, title: "" }); // 클릭한 좌표 추가
  };

  const handleChange = (e: ChangeEvent<HTMLInputElement>, id: number) => {
    setTitle(id, e.target.value);
    setShowPicker(false);
  };

  useEffect(() => {
    inputRef?.current?.focus();
    setShowPicker(true);
  }, [points]);

  const handleClickEmoji: MouseDownEvent = (emoji: EmojiClickData, e: MouseEvent) => {
    setTitle(Number(inputRef.current?.id), emoji.emoji);
  };
  return (
    <GraphContainer onClick={handleClick}>
      {points.map((point) => (
        <Point
          key={point.id}
          x={point.x}
          y={point.y}
          title={point.title} // input.value로 전달하는 값
          onChange={(e) => handleChange(e, point.id)}
          ref={inputRef}
        />
      ))}
      <EmojiPicker
        open={showPicker}
        onEmojiClick={handleClickEmoji}
        style={{
          position: "absolute",
          top: `${points.at(-1)?.y}px`,
          left: `${points.at(-1)?.x}px`,
        }}
      />
    </GraphContainer>
  );
};

export default Graph;

 

 

고민하다가 결국 지피티로 해결했다.

 

우선, 위 코드의 잘못된 점

  useEffect(() => {
    inputRef?.current?.focus();
    setShowPicker(true);
  }, [points]);

  const handleClickEmoji: MouseDownEvent = (emoji: EmojiClickData, e: MouseEvent) => {
    setTitle(Number(inputRef.current?.id), emoji.emoji);
  };

1. 잘못된 inputRef 사용

이전에 구현한 코드에서 points 배열에 새로운 point가 추가될때마다 마지막 point에 focus 를 주도록 함

즉 inputRef 는 항상 마지막에 렌더링된 input을 가리키고 있다. 

따라서 inputRef.current.id 로 접근해서 emoji를 추가하는건 유저가 수정하려는 input이 아닐 수 있다.

 

      <EmojiPicker
        open={showPicker}
        onEmojiClick={handleClickEmoji}
        style={{
          position: "absolute",
          top: `${points.at(-1)?.y}px`,
          left: `${points.at(-1)?.x}px`,
        }}
      />

2. open  = {true} 로 하되, Picker 컴포넌트를 조건부 렌더링을 하자

 


변경한 내용

1. 선택한 input의 id 를 로컬 상태로 관리하자

-> 선택된 값이 있을때 Picker 열도록 한다.

-> setTitle에 선택된 id를 넘겨줄수 있다.

// 화면을 클릭해서 Point 컴포넌트를 생성하는 핸들러
const handleClick = (e: ReactMouseEvent<HTMLDivElement>) => {
  const rect = e.currentTarget.getBoundingClientRect();
  const x = e.clientX - rect.left;
  const y = e.clientY - rect.top;
  const newPoint = { id: Date.now(), x, y, title: "" };

  addPoint(newPoint);
  setActivePoint(newPoint.id); // 새로 추가한 포인트를 활성화
};

// Point 내부의 input value를 관리하는 핸들러
const handleChange = (e: ChangeEvent<HTMLInputElement>, id: number) => {
  setTitle(id, e.target.value);
  setActivePoint(null); // 입력이 끝나면 이모지 피커 닫기
};

그래프를 클릭해서 Point 컴포넌트를 추가하는 handleClick 핸들러에서

새롭게 추가하는 id 값을 activePoint 로 저장한다.

 

input change핸들러에서 setTitle은 그대로 유지하고, activePoint = null 로 할당해서 Picker 닫기 

{activePoint && (
        <EmojiPicker
          open={true}
          onEmojiClick={handleClickEmoji}
          style={{
            position: "absolute",
            top: `${points.find((p) => p.id === activePointId)?.y}px`,
            left: `${points.find((p) => p.id === activePointId)?.x}px`,
          }}
        />
      )}

 

setShowPicker, ref 로 구현하려고 했던 두 가지 역할을 activePoint라는 활성화된 특정 id값으로 구현하게 되었다. 

 

이미지를 클릭하면 사이트로 이동합니다.

 

++ 이모지를 선택하지 않고 다음 점을 찍고, 이모지 선택하면 자꾸만 새로 생긴 점이 아닌 이전 점에 이모지가  title로 들어가는 문제가 생겼는데 해결하지 못해 이모지 피커를 빼버렸다 ㅠ 그리고 기본 값으로 📍핀 이모지를 넣어주는 것으로 마무리했다..