๐ŸŽ€ Wedding-card #6. Main Page - comment

Graceยท2021๋…„ 7์›” 19์ผ
0

wedding-card

๋ชฉ๋ก ๋ณด๊ธฐ
6/7
post-thumbnail

๐Ÿ“Œ comment(๋Œ“๊ธ€) ๊ธฐ๋Šฅ

์‹œ์ค‘์—(?) ๋„๋ ค์žˆ๋Š” ๋ชจ๋ฐ”์ผ ์ฒญ์ฒฉ์žฅ์—์„œ ์ œ์ผ ๋ฐ”๊พธ๊ณ  ์‹ถ์—ˆ๋˜ ๊ธฐ๋Šฅ..
๋Œ€๋ถ€๋ถ„ ์–ด๋–ค ๊ธ€์“ฐ๋Š” ํ˜•์‹์˜ ํผ์— ๋‚ด์šฉ์„ ์ž…๋ ฅํ•˜๋ฉด
์‹ ๋ž‘/์‹ ๋ถ€์˜ ์ด๋ฉ”์ผ๋กœ ์ „์†ก๋˜๊ฒŒ ํ•˜๋Š” ๊ธฐ๋Šฅ์„ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ์—ˆ๋‹ค.

๊ทผ๋ฐ ๋ˆ„๊ฐ€.. ๊ทธ๊ฑธ ๊ตณ์ด.. ์ด๋ฉ”์ผ๋กœ ์ถ•ํ•˜๋ฉ”์„ธ์ง€๋ฅผ ๋ณด๋‚ด๊ณ  ์žˆ์„๊นŒ ์‹ถ์–ด์„œ..
์ธ์Šคํƒ€๊ทธ๋žจ์ฒ˜๋Ÿผ ๋‹ค๋ฅธ์‚ฌ๋žŒ๋“ค๋„ ๋ˆ„๊ตฐ๊ฐ€์˜ ์ถ•ํ•˜๋ฉ”์„ธ์ง€๋ฅผ ํ•จ๊ป˜ ๊ณต์œ ํ•ด๋„ ์ข‹์ง€ ์•Š์„๊นŒ ์‹ถ์–ด์„œ
๋Œ“๊ธ€์ฒ˜๋Ÿผ ๋ณด์ด๊ฒŒ ๋งŒ๋“ค๊ธฐ๋กœ ํ–ˆ๋‹ค!


1. ๋Œ“๊ธ€ ํผ ๋งŒ๋“ค๊ธฐ

์šฐ์„  ์—ฌ๋Ÿฌ๊ฐœ์˜ input์„ ๊ด€๋ฆฌํ•˜๋Š” ์ž‘์—…์„ ๋งŽ์ด ํ•ด๋ณด์ง€ ์•Š์•˜๊ธฐ ๋•Œ๋ฌธ์—
codesandbox์—์„œ ์—ฐ์Šต์„ ๋จผ์ € ํ•ด๋ณด์•˜๋‹ค..
์ธ์Šคํƒ€์ฒ˜๋Ÿผ ์ด๋ฆ„(bold์ฒด) ๋Œ“๊ธ€๋‚ด์šฉ ํผ์œผ๋กœ ๋งŒ๋“ค์–ด์•ผ ํ–ˆ๊ธฐ ๋•Œ๋ฌธ์—

  • ์ด๋ฆ„ : username
  • ๋‚ด์šฉ : content

๋ผ๋Š” ์ด๋ฆ„์œผ๋กœ input 2๊ฐœ ๋งŒ๋“ค๊ธฐ!

๊ทธ๋ž˜์„œ ์™„์„ฑํ•œ ์ฝ”๋“œ๋Š”

export default function CommentComponent() {
  const [inputs, setInputs] = useState({
    username: "",
    content: "",
  });
  
  const handleChange = () => {
    const {name, value} = e.target; //e.target์—์„œ name๊ณผ value๋ฅผ ์ถ”์ถœ(?)
    setInputs({
      ...inputs, // ๊ธฐ์กด input ๊ฐ์ฒด๋ฅผ ๋ณต์‚ฌ
      [name] : value, // name ํ‚ค๋ฅผ ๊ฐ€์ง„ ๊ฐ’์„ value๊ฐ’์œผ๋กœ ์„ค์ •
    });
  };
  
  const handleSubmit = () => {
    e.preventDefault();
    if(!inputs.username || !inputs.content) return null; // ๋นˆ์นธ์ผ๊ฒฝ์šฐ์—” ๋ฐ˜์‘ ์—†์Œ
    addTweet(inputs) // ๋Œ“๊ธ€ ์ถ”๊ฐ€๋˜๋Š” ํ•จ์ˆ˜ - ๋ฐ‘์—์„œ ์ž์„ธํžˆ ์ •๋ฆฌ
    setInputs({
      username: "",
      content: "",
    });
  };
  
  ...
  return (
    ...
    <form>
      <NameInput 
    	name="username"
    	placeholder="์ด๋ฆ„์„ ๋„ฃ์–ด์ฃผ์„ธ์š”"
    	value={inputs.username}
		onChange={handleChange}
	  />
      <ContentInput 
		name="content"
		placeholder="๋Œ“๊ธ€ ๋‹ฌ๊ธฐ..."
		value={inputs.content}
		onChange={handleChange}
	  />
      <button type="submit">๊ฒŒ์‹œ</button>
    </form>

2. ๋Œ“๊ธ€ ์ถ”๊ฐ€ํ•˜๋Š” ์ฝ”๋“œ ์ž‘์„ฑ

์„œ๋ฒ„์™€์˜ ์—ฐ๊ฒฐ, ๋ฐฐ์—ด์—์„œ ์ถ”๊ฐ€๋˜๋Š” ์ฝ”๋“œ, propsํƒ€์ž… ๋“ฑ๋“ฑ ์—ฌ๋Ÿฌ๊ฐ€์ง€๋ฅผ ํ•ฉ์ณ์„œ ์ƒ๊ฐํ•˜๋‹ค๋ณด๋‹ˆ
์กฐ๊ธˆ ์–ด๋ ค์› ๋˜ ๋ถ€๋ถ„....ใ… ใ… 

  1. ์šฐ์„  MainPage์—์„œ ์„œ๋ฒ„์—์„œ ๋ฐ›์•„์˜จ state๊ฐ’์ธ comments๋ฅผ props๋กœ ๋ฐ›์•„์˜ค๊ธฐ
  2. comments๋ฅผ mapํ•จ์ˆ˜๋กœ ๋Œ๋ ค ์ƒˆ๋กœ์šด comment๋ฐฐ์—ด์„ ๋งŒ๋“ค๊ธฐ
  3. comment๋ฐฐ์—ด์„ CommentOutput์ด๋ผ๋Š” ๊ฐ ๋Œ“๊ธ€์˜ view๋ฅผ ๋‹ด๋‹นํ•˜๋Š” ์ปดํฌ๋„ŒํŠธ์— props๋กœ ๋„˜๊ฒจ์ฃผ๊ธฐ
  4. ์ƒˆ๋กœ์šด comment์—์„œ username์€ ์ด๋ฆ„, text๋Š” ๋Œ“๊ธ€ ๋‚ด์šฉ์œผ๋กœ,
    ๋Œ“๊ธ€์ฒ˜๋Ÿผ ๋งŒ๋“ค์–ด์ฃผ๋Š” ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋žœ๋”๋ง ์‹œ์ผœ์ฃผ๊ธฐ

์ด๋•Œ, ์ถ”๊ฐ€ํ•ด์ฃผ๋Š” ํ•จ์ˆ˜์— ์ƒˆ comment๋ฐฐ์—ด์„ ์ธ์ž๋กœ ๋„˜๊ฒจ์ค„ ๋•Œ
์„œ๋ฒ„์—์„œ ๋„˜๊ฒจ์ค€ ๊ฐ’๊ณผ ๋™์ผํ•˜๊ฒŒ ๋„˜๊ฒจ์ฃผ์–ด์•ผ ํ•œ๋‹ค(๊ณ  ethan์ด ๋งํ–ˆ๋˜ ๊ฒƒ ๊ฐ™๋‹ค..)

์ฝ”๋“œ๋ฅผ ๋ณด์ž๋ฉด

type CommentOutputProps = {
  comment: CommentType;
};

// ๊ฐœ๋ณ„ ๋Œ“๊ธ€์˜ component
function CommentOutput(props: CommentOutputProps) {
  const { comment } = props;
  return (
    <SingleComment>
      <div>
        <span style={{ fontWeight: "bold", marginRight: "8px" }}>
          {comment.username}
        </span>
        <span>{comment.text}</span>
      </div>
    </SingleComment>
  );
}

// ๋Œ“๊ธ€ ์ถ”๊ฐ€ํ•˜๋Š” ํ•จ์ˆ˜(addTweet)์˜ ํƒ€์ž…
type AddComment = (state: InputState) => void;

type CommentStateProps = {
  comments: CommentType[];
};
export default function CommentComponent() {
  ...
  const dispatch = useDispatch();

  const addTweet: AddComment = (args) => {
    const { username, content } = args;
    const comment = {
      postId: "",
      username: username,
      text: content,
    };
    dispatch(addComment(comment));
    fetch("์„œ๋ฒ„์ฃผ์†Œ/addComment", {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
      },
      body: JSON.stringify(comment),
    })
      .then((response) => response.json())
  };
  
  return (
    <Container>
      {comments.map((comment) => {
    	return <CommentOutput comment={comment} />;
      )}
    ...   
    </Container>
  );
}

์•„์ง๋„ POST ๋ฉ”์†Œ๋“œ๋กœ ์„œ๋ฒ„์— ๋ฐ์ดํ„ฐ๋ฅผ ๋ณด๋‚ด๋Š” ์ž‘์—…์ด ์ต์ˆ™ํ•˜์ง€ ์•Š์ง€๋งŒ,
์ข‹์•„์š” ๊ธฐ๋Šฅ๊ณผ๋Š” ๋‹ค๋ฅด๊ฒŒ body์— comment๋ผ๋Š” ๊ฐ’์„ ๋ณด๋‚ด์•ผ ํ•œ๋‹ค๋Š”๊ฑธ ์•Œ๊ฒŒ ๋˜์—ˆ๋‹ค.
์ข€ ๋” ์—ฐ์Šตํ•ด๋ด์•ผ๊ฒ ์ง€๋งŒ, postman์—์„œ ์–ด๋–ค ์ •๋ณด๋ฅผ ์–ด๋–ป๊ฒŒ ๋ด์•ผ ํ•˜๋Š”์ง€๋„ ์กฐ๊ธˆ์€ ์•Œ๊ฒŒ ๋˜์—ˆ์œผ๋‹ˆ
๋‹ค์Œ ์ž‘์—…๋•Œ๋Š” ๊ทธ๋ž˜๋„ ์ข€ ์ต์ˆ™ํ•˜๊ฒŒ ํ•  ์ˆ˜ ์žˆ๋„๋ก
๋” ๊ณต๋ถ€ํ•ด์•ผ๊ฒ ๋‹ค.

3. ๋ฆฌํŒฉํ† ๋ง

์ž‘์—…์„ ์–ด๋Š์ •๋„ ํ•˜๋‹ค๋ณด๋‹ˆ ๋Œ“๊ธ€ ๊ธธ์ด๋‚˜ ์–‘์ด ๋Š˜์–ด๋‚˜๋ฉด ๋ฌธ์ œ๊ฐ€ ์ƒ๊ฒผ๋‹ค.

  • ๊ธธ์ด๊ฐ€ ๊ธธ์–ด์งˆ ๊ฒฝ์šฐ, ์ด๋ฆ„๊ณผ ๋Œ“๊ธ€๋‚ด์šฉ์˜ ๊ณต๊ฐ„์ด ๋‚˜๋ˆ„์–ด์ ธ์žˆ๊ธฐ ๋•Œ๋ฌธ์—

    ์ด๋Ÿฐ์‹์œผ๋กœ ์ด๋ฆ„์ด ๋ฐ‘์œผ๋กœ ๋‚ด๋ ค๊ฐ€๋ฒ„๋ฆฌ๋Š” ํ˜„์ƒ์ด...
    ์–ด์ฐŒ์ €์ฐŒ ํ•œ์ค„๋กœ ๋žœ๋”๋ง์ด ๋˜๋”๋ผ๋„, ์ธ์Šคํƒ€๊ทธ๋žจ์ฒ˜๋Ÿผ

    ์‚ฌ์šฉ์ž์ด๋ฆ„ ์•ˆ๋…•๋‚ด์ด๋ฆ„์€---์ด์•ผ๋งŒ๋‚˜์„œ๋ฐ˜๊ฐ€์›Œ
    ์ด๋ ‡๊ฒŒ ๋žœ๋”๋ง๋˜์•ผ๋˜๋Š”๋ฐ ์–ด๋ ต๋„ค

์ด๋ฆ„ ๋ฐ‘์œผ๋กœ๋„ ๋‚ด์šฉ์ด ๋“ค์–ด๊ฐ€์•ผ ํ•˜๋Š”๋ฐ ๊ตฌ์—ญ์ด ๋‚˜๋ˆ„์–ด์ ธ์„œ ์ž๊พธ๋งŒ

์ด๋ ‡๊ฒŒ๋ฐ–์—...

๊ทธ๋ž˜์„œ ์—„์ฒญ ์˜ค๋žซ๋™์•ˆ ์‚ฝ์งˆ์„ ํ–ˆ๋Š”๋ฐ, ethan์ด ํ•œ๋ฐฉ์— ํ•ด๊ฒฐํ•ด๋ฒ„๋ ค์„œ ์–ด์ด๊ฐ€ ์—†์—ˆ๋‹ค..
๋ฐฉ๋ฒ•์€ block์š”์†Œ์ธpํƒœ๊ทธ๋กœ ์ด๋ฆ„/๋Œ“๊ธ€๋‚ด์šฉ ์„ inline์š”์†Œ์ธspanํƒœ๊ทธ๋ฅผ ๊ฐ์‹ธ๋‹ˆ ๋ฐ”๋กœ ๊ฐ€๋Šฅํ•ด์กŒ๋‹ค...
์ด๊ฒŒ ๋‹ค css์ง€์‹์ด ์–•์€ ํƒ“์ด๋ผ๊ณ  ์ƒ๊ฐํ•˜๊ณ 
๋” ๊ณต๋ถ€ํ•˜๊ธฐ๋กœ..!

  • ์–‘์ด ๋Š˜์–ด๋‚  ๊ฒฝ์šฐ, ์ฒซ ๋žœ๋”๋ง ํ™”๋ฉด์—์„œ ์Šคํฌ๋กค ๊ธธ์ด๊ฐ€ ๋„ˆ๋ฌด ๊ธธ์–ด์ง

ํ…Œ์ŠคํŠธ๋ฅผ ํ•œ๋‹ค๊ณ  ์„œ๋ฒ„์— ์—ฐ๊ฒฐํ•œ์ฑ„๋กœ ๊ณ„์† ๋Œ“๊ธ€์„ ์ถ”๊ฐ€ํ•˜๋‹ค๊ฐ€
40๊ฐœ๊ฐ€ ๋„˜์–ด๋ฒ„๋ ธ๋Š”๋ฐ, ๊ทธ๋ ‡๊ฒŒ ๋˜๋‹ˆ๊นŒ ์ฒซ ๋žœ๋”๋ง ํ™”๋ฉด์ธ Mainpage๊ฐ€ ๋„ˆ๋ฌด ์ง€์ ธ๋ถ„ํ•ด๋ณด์˜€๋‹ค.
๊ทธ๋ž˜์„œ ์ธ์Šคํƒ€๊ทธ๋žจ์ฒ˜๋Ÿผ ๋Œ“๊ธ€์„ ์ ‘์–ด์„œ ์ˆจ๊ฒจ๋ฒ„๋ฆฌ๊ธฐ๋กœ ํ–ˆ๋‹ค.
๋ฌผ๋ก  ๋˜‘๊ฐ™์ด ํŽ˜์ด์ง€ ์ด๋™์ฒ˜๋Ÿผ ๋ณด์ด๊ฒŒ๋Š” ์•„๋‹ˆ์ง€๋งŒ
๋ฒ„ํŠผ์„ ํด๋ฆญํ•จ์œผ๋กœ์„œ ๋Œ“๊ธ€์ด ์ ‘ํ˜”๋‹ค/ํŽด์ง€๋Š” ํด๋”๊ธฐ๋Šฅ์„ ๊ตฌํ˜„ํ•ด๋ณด์•˜๋‹ค.

isActive๋ผ๋Š” ์ƒํƒœ๊ฐ’(boolean)์„ ์‚ฌ์šฉํ•ด์„œ ํด๋ฆญํ•  ๋•Œ๋งˆ๋‹ค ์ƒํƒœ๊ฐ€ ๋ฐ”๋€Œ๋„๋ก ์ฝ”๋“œ๋ฅผ ์งฐ๋‹ค.
๋™์‹œ์— ๋Œ“๊ธ€๋ถ€๋ถ„ component์— visible์ด๋ผ๋Š” props๋ฅผ ์ „๋‹ฌํ•ด์„œ
visible๊ฐ’์ด false์ผ ๊ฒฝ์šฐ display: none, visibility: hidden์œผ๋กœ ์„ค์ •ํ•˜๋Š”
css ์š”์†Œ๋ฅผ ์กฐ์ •ํ•˜์˜€๋‹ค.

...
<CommentBox>
 <FolderSpan onClick={handleFolder}>
   ๋Œ“๊ธ€ {count}๊ฐœ {visible ? "์ ‘๊ธฐ" : "๋ชจ๋‘ ๋ณด๊ธฐ"}
 </FolderSpan>
    {visible
      ? comments.map((comment) => {
      return <CommentOutput comment={comment} />;
    })
    : null}
</CommentBox>
<CommentFooter visible={visible}>
  ...
</CommentFooter>

๊ทธ๋ฆฌ๊ณ  ์ถ”๊ฐ€๋กœ ๋Œ“๊ธ€์ด ์ ‘ํ˜€์žˆ์„ ๋• comment์˜ length๋ฅผ ํ†ตํ•ด ๊ฐฏ์ˆ˜๋ฅผ ํ‘œ์‹œํ•ด์ฃผ์—ˆ๋‹ค.

์ด๋ ‡๊ฒŒ ์ž‘์—…์„ ํ•˜๊ณ  ๋‚˜๋‹ˆ ๋˜ ์ƒ๊ธฐ๋Š” ์š•์‹ฌ..
๋Œ“๊ธ€์„ ํŽผ์ณค์„ ๋•Œ, ๋ฐ”๋กœ ์ด๋ฆ„ input์œผ๋กœ ํฌ์ปค์Šค๋ฅผ ๋งž์ถ”๊ณ  ์‹ถ์—ˆ๋Š”๋ฐ
useRef๋กœ ์ž‘์—…ํ–ˆ๋˜ ์ ์ด ์žˆ์—ˆ๋Š”๋ฐ ์ž˜ ์•ˆ๋˜๋Š”๊ฑฐ๋‹ค...
๊ทธ๋Ÿฌ๋‹ค๊ฐ€ useEffect์™€ ํ•ฉ์ณ์„œ ์Šคํฌ๋กค์„ ๋งจ ์•„๋ž˜๋กœ ๋‚ด๋ฆฐ ๋’ค ํฌ์ปค์Šค๋ฅผ ์ฃผ๋Š” ๋ฐฉ๋ฒ•์„ ์ฐพ์•˜๋‹ค.

...
const inputRef = useRef<HTMLInputElement>(null);

useEffect(() => {
  if(visible) {
    window.scrollTo(0, document.body.scrollHeight);
    if(inputRef.current) inputRef.current?.focus();
  }
}, [visible]);
...
<input 
  ref={inputRef}
  name="username"
  ...
/>

4. toast message ๊ธฐ๋Šฅ

์ข‹์•„์š” ๊ธฐ๋Šฅ์—์„œ๋„ ๋งํ–ˆ์ง€๋งŒ, ์„œ๋ฒ„์™€ ์—ฐ๋™ํ•ด์„œ view๋ฅผ ๋ณด์—ฌ์ฃผ๋‹ค๋ณด๋‹ˆ
์„œ๋ฒ„์— ์—…๋กœ๋“œ ๋˜๋Š” ์‚ฌ์ด์— ์‚ฌ์šฉ์ž์—๊ฒŒ ์ž‘์—…์ด ์ง„ํ–‰์ค‘์ด๋ผ๊ฑฐ๋‚˜, ์™„๋ฃŒ๋˜์—ˆ๋‹ค๋Š” ํ‘œ์‹œ๋ฅผ ํ•ด์ฃผ๋Š”๊ฒŒ ํ•„์š”ํ–ˆ๋‹ค.
๊ทธ๋ž˜์„œ ํ† ์ŠคํŠธ๋ฉ”์„ธ์ง€๋ฅผ ๋„์›Œ์ฃผ๊ธฐ๋กœ ํ•˜๊ณ  ์ง์ ‘ ๊ตฌํ˜„ํ•ด๋ณด๊ธฐ๋กœ ํ–ˆ๋‹ค.

isActive๋ผ๋Š” ์ƒํƒœ๊ฐ’, ๊ทธ ๊ฐ’์„ ๋ณ€๊ฒฝํ•  ์ˆ˜ ์žˆ๋Š” setIsActiveํ•จ์ˆ˜๋ฅผ ๋งŒ๋“ค์–ด์„œ
ToastMessage ์ปดํฌ๋„ŒํŠธ์— props๋กœ ์ „๋‹ฌํ•˜์˜€๋‹ค.
๊ทธ๋ฆฌ๊ณ  ๊ฒŒ์‹œ๋ฅผ ํด๋ฆญํ•˜๋ฉด ํ† ์ŠคํŠธ ๋ฉ”์„ธ์ง€๊ฐ€ ๋„์›Œ์ง€๋„๋ก button์ปดํฌ๋„ŒํŠธ์—
onClick={() => setIsActive(true)}
์ฝ”๋“œ๋ฅผ ์ถ”๊ฐ€ํ•ด์ฃผ์—ˆ๋‹ค.

// ToastMessage.tsx
type ToastProps = {
  children: string;
  isActive: boolean;
  setIsActive: (a: boolean) => void;
};

export default function ToastMessage(props: ToastProps) {
  const { children, isActive, setIsActive } = props;
  // 3์ดˆํ›„์— ๋ฉ”์„ธ์ง€ ์‚ฌ๋ผ์ง€๊ฒŒ ํ•˜๊ธฐ
  useEffect(() => {
    if (isActive === true) {
      setTimeout(() => {
        setIsActive(false);
      }, 3000);
    }
  });
  return (
    <Fragment>
      {isActive ? (
        <Toast show={true}>{children}</Toast>
      ) : (
        <Toast show={false} />
      )}
    </Fragment>
  );
}

5. ๊ฒฐ๊ณผ๋ฌผ

๋•๋ถ„์— ์ž‘์—…ํ•˜์˜€์Šต๋‹ˆ๋‹ค :)

profile
์‰ฝ๊ฒŒ ์‚ฌ๋Š”๊ฑด ์žฌ๋ฏธ๊ฐ€ ์—†๋”๊ตฐ์š”, ์ƒˆ๋กœ ์‹œ์ž‘ํ•ฉ๋‹ˆ๋‹ค๐Ÿค“

0๊ฐœ์˜ ๋Œ“๊ธ€