앱 전반의 상태 작업하기(React Context)

·2024년 4월 5일
0

NextJS

목록 보기
14/26
post-thumbnail

🔗 레파지토리에서 보기

📌 React Context 생성하기

📖 새로운 React Context 생성하기

💎 store/notification-context.js

import { createContext, useState } from "react";

const NotificationContext = createContext({
  notification: null, // {title, message, status}
  showNotification: function () {},
  hideNotification: function () {},
});

export function NotificationContextProvider({ children }) {
  return (
    <NotificationContext.Provider>{children}</NotificationContext.Provider>
  );
}

export default NotificationContext;

💎 pages/_app.js

import Head from "next/head";

import Layout from "../components/layout/layout";
import Notification from "../components/ui/notification.js";
import { NotificationContextProvider } from "../store/notification-context.js";
import "../styles/globals.css";

function MyApp({ Component, pageProps }) {
  return (
    <NotificationContextProvider>
      <Layout>
        <Head>
          <title>Next Events</title>
          <meta name="description" content="NextJS Events" />
          <meta
            name="viewport"
            content="initial-scale=1.0, width=device-width"
          />
        </Head>
        <Component {...pageProps} />
        <Notification title="Test" message="This is a test." status="pending" />
      </Layout>
    </NotificationContextProvider>
  );
}

export default MyApp;

📖 콘텍스트 상태 추가하기

💎 store/notification-context.js

import { createContext, useState } from "react";

const NotificationContext = createContext({
  notification: null, // {title, message, status}
  showNotification: function (notificationData) {},
  hideNotification: function () {},
});

export function NotificationContextProvider({ children }) {
  const [activeNotification, setActiveNotification] = useState();

  function showNotificationHandler(notificationData) {
    setActiveNotification(notificationData);
  }

  function hideNotificationHandler() {
    setActiveNotification(null);
  }

  const context = {
    notification: activeNotification,
    showNotification: showNotificationHandler,
    hideNotification: hideNotificationHandler,
  };

  return (
    <NotificationContext.Provider value={context}>
      {children}
    </NotificationContext.Provider>
  );
}

export default NotificationContext;

📖 컴포넌트 내에서 콘텍스트 데이터 사용하기

  • Layout 자체가 콘텐츠를 감싸는 wrapper이므로 이 안에 Notification을 추가할 것.
import { Fragment, useContext } from "react";

import MainHeader from "./main-header";
import Notification from "../ui/notification";
import NotificationContext from "../../store/notification-context";

function Layout(props) {
  const NotificationCtx = useContext(NotificationContext);

  const activeNotification = NotificationCtx.notification;
  return (
    <Fragment>
      <MainHeader />
      <main>{props.children}</main>
      {activeNotification && (
        <Notification
          title={activeNotification.title}
          message={activeNotification.message}
          status={activeNotification.status}
        />
      )}
    </Fragment>
  );
}

export default Layout;

📖 예시 : 알림 트리거 & 표시하기

💎 components/input/newsletter-registration.js

import { useRef, useContext } from "react";
import classes from "./newsletter-registration.module.css";
import NotificationContext from "../../store/notification-context.js";

function NewsletterRegistration() {
  const emailInputRef = useRef();
  const notificationCtx = useContext(NotificationContext);

  function registrationHandler(event) {
    event.preventDefault();

    const enteredEmail = emailInputRef.current.value;

    notificationCtx.showNotification({
      title: "구독 신청 중...",
      message: "뉴스레터 구독 신청 중",
      status: "pending",
    });

    fetch("api/newsletter", {
      method: "POST",
      body: JSON.stringify({ email: enteredEmail }),
      headers: {
        "Content-Type": "application/json",
      },
    })
      .then((response) => {
        if (response.ok) {
          return response.json();
        }

        return response.json().then((data) => {
          throw new Error(data.message || "문제가 발생했습니다!");
        });
      })
      .then((data) => {
        notificationCtx.showNotification({
          title: "구독 성공",
          message: "뉴스레터 구독 신청을 성공적으로 마쳤습니다!",
          status: "success",
        });
        console.log(data);
        emailInputRef.current.value = null;
      })
      .catch((error) => {
        notificationCtx.showNotification({
          title: "구독 실패",
          message: error.message || "뉴스레터 구독하는데 실패했습니다.",
          status: "error",
        });
      });
  }

  return (
    <section className={classes.newsletter}>
      <h2>Sign up to stay updated!</h2>
      <form onSubmit={registrationHandler}>
        <div className={classes.control}>
          <input
            type="email"
            id="email"
            placeholder="Your email"
            aria-label="Your email"
            ref={emailInputRef}
          />
          <button>Register</button>
        </div>
      </form>
    </section>
  );
}

export default NewsletterRegistration;
  • 뉴스레터에 대한 알림 창(pending, success, error)를 실행할 수 있도록 작성


📖 예시 : 알림 자동으로 제거하기

💎 1. notification을 클릭했을 때 제거하기

import { useContext } from "react";

import classes from "./notification.module.css";
import NotificationContext from "../../store/notification-context.js";

function Notification(props) {
  const notificationCtx = useContext(NotificationContext);

  const { title, message, status } = props;

  let statusClasses = "";

  if (status === "success") {
    statusClasses = classes.success;
  }

  if (status === "error") {
    statusClasses = classes.error;
  }

  if (status === "pending") {
    statusClasses = classes.pending;
  }

  const activeClasses = `${classes.notification} ${statusClasses}`;

  return (
    <div className={activeClasses} onClick={notificationCtx.hideNotification}>
      <h2>{title}</h2>
      <p>{message}</p>
    </div>
  );
}

export default Notification;
  • onClick을 추가하여 클릭했을 때 notificationCtx.hideNotificationHandler가 작동하도록 한다.

💎 2. 타이머를 이용하여 자동으로 제거하기

import { createContext, useState, useEffect } from "react";

const NotificationContext = createContext({
  notification: null, // {title, message, status}
  showNotification: function (notificationData) {},
  hideNotification: function () {},
});

export function NotificationContextProvider({ children }) {
  const [activeNotification, setActiveNotification] = useState();

  function showNotificationHandler(notificationData) {
    setActiveNotification(notificationData);
  }

  function hideNotificationHandler() {
    setActiveNotification(null);
  }

  useEffect(() => {
    if (
      activeNotification &&
      (activeNotification.status === "success" ||
        activeNotification.status === "error")
    ) {
      const timer = setTimeout(() => {
        hideNotificationHandler();
      }, 3000);

      return () => {
        clearTimeout(timer);
      };
    }
  }, [activeNotification]);

  const context = {
    notification: activeNotification,
    showNotification: showNotificationHandler,
    hideNotification: hideNotificationHandler,
  };

  return (
    <NotificationContext.Provider value={context}>
      {children}
    </NotificationContext.Provider>
  );
}

export default NotificationContext;


📖 과제 : 댓글의 notification 표시하기 + 댓글 창의 로딩 메시지 띄우기

💎 스스로 해결하기

// components/input/comments.js
import { useEffect, useState, useContext } from "react";

import CommentList from "./comment-list";
import NewComment from "./new-comment";
import classes from "./comments.module.css";

import NotificationContext from "../../store/notification-context";

function Comments(props) {
  const { eventId } = props;
  const [comments, setComments] = useState([]);
  const [showComments, setShowComments] = useState(false);
  const notificationCtx = useContext(NotificationContext);

  useEffect(() => {
    if (showComments) {
      notificationCtx.showNotification({
        title: "Loading..",
        message: "댓글 불러오는 중 입니다.",
        status: "pending",
      });
      fetch(`/api/comments/${eventId}`)
        .then((response) => {
          if (response.ok) {
            return response.json();
          }

          return response.json().then((data) => {
            throw new Error(data.message || "댓글을 불러오는데 실패했습니다.");
          });
        })
        .then((data) => {
          setComments(data.comments);
          notificationCtx.showNotification({
            title: "Success!",
            message: "댓글 불러오는데 성공했습니다.",
            status: "success",
          });
        })
        .catch((error) => {
          notificationCtx.showNotification({
            title: "Error!",
            message: error.message || "댓글 불러오는데 실패했습니다.",
            status: "error",
          });
        });
    }
  }, [showComments]);

  function toggleCommentsHandler() {
    setShowComments((prevStatus) => !prevStatus);
  }

  function addCommentHandler(commentData) {
    notificationCtx.showNotification({
      title: "댓글 등록 중...",
      message: "댓글 작성 중",
      status: "pending",
    });

    // send data to API
    fetch(`/api/comments/${eventId}`, {
      method: "POST",
      body: JSON.stringify(commentData),
      headers: { "Content-Type": "application/json" },
    })
      .then((response) => {
        if (response.ok) {
          return response.json();
        }

        return response.json().then((data) => {
          throw new Error(data.message || "댓글 작성하는데 실패했습니다.");
        });
      })
      .then((data) => {
        notificationCtx.showNotification({
          title: "댓글 작성 성공",
          message: "댓글 작성 성공!",
          status: "success",
        });
      })
      .catch((error) => {
        notificationCtx.showNotification({
          title: "댓글 작성 실패",
          message: error.message || "댓글 작성 실패했습니다. 다시 시도해주세요",
          status: "error",
        });
      });
  }

  return (
    <section className={classes.comments}>
      <button onClick={toggleCommentsHandler}>
        {showComments ? "Hide" : "Show"} Comments
      </button>
      {showComments && <NewComment onAddComment={addCommentHandler} />}
      {showComments && <CommentList items={comments} />}
    </section>
  );
}

export default Comments;

💎 강사 코드

// components/input/comments.js
import { useEffect, useState, useContext } from "react";

import CommentList from "./comment-list";
import NewComment from "./new-comment";
import classes from "./comments.module.css";

import NotificationContext from "../../store/notification-context";

function Comments(props) {
  const { eventId } = props;
  const [comments, setComments] = useState([]);
  const [showComments, setShowComments] = useState(false);
  const notificationCtx = useContext(NotificationContext);
  const [isFetchingComments, setIsFetchinComments] = useState(false);

  useEffect(() => {
    if (showComments) {
      setIsFetchinComments(true);
      fetch(`/api/comments/${eventId}`)
        .then((response) => response.json())
        .then((data) => {
          setComments(data.comments);
          setIsFetchinComments(false);
        });
    }
  }, [showComments]);

  function toggleCommentsHandler() {
    setShowComments((prevStatus) => !prevStatus);
  }

  function addCommentHandler(commentData) {
    notificationCtx.showNotification({
      title: "댓글 등록 중...",
      message: "댓글 작성 중",
      status: "pending",
    });

    // send data to API
    fetch(`/api/comments/${eventId}`, {
      method: "POST",
      body: JSON.stringify(commentData),
      headers: { "Content-Type": "application/json" },
    })
      .then((response) => {
        if (response.ok) {
          return response.json();
        }

        return response.json().then((data) => {
          throw new Error(data.message || "댓글 작성하는데 실패했습니다.");
        });
      })
      .then((data) => {
        notificationCtx.showNotification({
          title: "댓글 작성 성공",
          message: "댓글 작성 성공!",
          status: "success",
        });
      })
      .catch((error) => {
        notificationCtx.showNotification({
          title: "댓글 작성 실패",
          message: error.message || "댓글 작성 실패했습니다. 다시 시도해주세요",
          status: "error",
        });
      });
  }

  return (
    <section className={classes.comments}>
      <button onClick={toggleCommentsHandler}>
        {showComments ? "Hide" : "Show"} Comments
      </button>
      {showComments && <NewComment onAddComment={addCommentHandler} />}
      {showComments && !isFetchingComments && <CommentList items={comments} />}
      {showComments && isFetchingComments && <p>Loading...</p>}
    </section>
  );
}

export default Comments;

0개의 댓글

관련 채용 정보