FE-SPRINT-REACT-HOOKS

leekoby·2023년 3월 24일
1

CodeStates

목록 보기
2/6
post-thumbnail

📌들어가기에 앞서

해당 포스트는 CodeStates 과정중의 내용을 복습하며 정리한 내용입니다.


Getting Started

이번 과제는 이번 유닛에서 배운 React Hooks를 적용해보고, Custom HookReact.lazy()Suspense를 이용하여 React 앱을 직접 리팩토링을 해봅니다.

어떤 부분을 Custom Hook으로 만들 수 있을 것인지, 어떻게 React.lazy()Suspense를 적용해볼 수 있을 것인지 페어와 함께 고민하며 앱을 리팩토링 해보세요.




React Hooks 적용하기

먼저 터미널을 열어 npm을 이용해 전역 설치를 해주자.

npm i -g json-server

그 다음에 sprint의 data에 들어가서 다음 명령어를 입력하자.

json-server --watch data.json --port 3001




App.js

App.js에서는 react.lazy()suspense를 사용하여 컴포넌트를 리팩토링하였다.

json 파일이 있는 data 폴더에서 서버를 실행시키고 json 파일을 이용하여

get,post 요청시 그에 맞게 응답해주는 형식이다.

먼저 app.js 에서 Route 설정한 컴포넌트를 lazy 로 바꾸고 Suspense로 감싼다.

Suspense, lazyimport해주는 것을 잊지말자.

import { BrowserRouter, Routes, Route } from 'react-router-dom';
import React, { Suspense, lazy, useEffect, useState } from 'react';
import useFetch from './util/useFetch'



const Home = lazy(() => import('./Home'));
const Navbar = lazy(() => import('./component/Navbar'));
const CreateBlog = lazy(() => import('./blogComponent/CreateBlog'));
const BlogDetails = lazy(() => import('./blogComponent/BlogDetail'));
const NotFound = lazy(() => import('./component/NotFound'));
const Footer = lazy(() => import('./component/Footer'));
const Loading = lazy(() => import('./component/Loading'))


function App() {
  const { blogs, isPending, error, } = useFetch(`http://localhost:3001/blogs/`)

  return (
    <BrowserRouter>
      {error && <div>{error}</div>}
      <Suspense fallback={<Loading />}>
        <div className="app">
          <Navbar />
          <div className="content">
            <Routes>
              <Route exact path="/" element={<Home blogs={blogs} isPending={isPending} />} />
              <Route path="/create" element={<CreateBlog />} />
              <Route path="/blogs/:id" element={<BlogDetails />} />
              {/* 이렇게 path에 *(와일드카드)를 넣으면 매치되는 URL이 없을 때 해당 컴포넌트를 보여줍니다. */}
              <Route path="*" element={<NotFound />} />
            </Routes>
          </div>
          <Footer />
        </div>
      </Suspense>
    </BrowserRouter>
  );
}
export default App;



BlogDetail.js

현재는 개별 블로그 내용으로 진입해도 내용이 보이지 않기 때문에 useParams로 작성한 내용의 id값을 받아와 해당 id 내용만 화면에 띄우도록한다.

delete 버튼을 누르면 다시 home으로 리다이렉트되도록 useNavigate()를 이용하여 로직을 작성하였고,

하트를 누르면 home에서 새로고침을 했을 때 숫자가 올라가도록 만들어 주었다.

작성글의 세부내용을 볼 때 스크롤이 최상단으로 위치할 수 있도록 useScroll을 작성했는데, 이 부분이 Hook이라고 할 수 있는지 의문이다.

import { useEffect, useState } from "react";
import { useNavigate, useParams } from "react-router-dom";
import useFetch from "../util/useFetch";


export const useScroll = (e) => {

  return window.scrollTo(0, 0)
}

const BlogDetails = () => {

  const { id } = useParams();
  const [isLike, setIsLike] = useState(true);
  const { blogs: blog, isPending, error } = useFetch(`http://localhost:3001/blogs/${id}`);
  const navigate = useNavigate();
  useScroll()

  const handleDeleteClick = () => {
    /* delete 버튼을 누르면 다시 home으로 리다이렉트 되어야 합니다. */
    /* useNavigate()를 이용하여 로직을 작성해주세요. */
    fetch(`http://localhost:3001/blogs/${id}`, {
      method: "DELETE",
      headers: {
        'Content-Type': 'application/json'
      },
    })
      .then(() => {
        navigate('/')
        window.location.reload()
      })
      .catch(err => console.log(err))


    console.log('delete!');
  }

  const handleLikeClick = () => {
    /* 하트를 누르면 home에서 새로고침을 했을 때 숫자가 올라가야 합니다. */
    /* isLike와 blog.likes를 이용하여 handleLikeClick의 로직을 작성해주세요. */
    setIsLike(!isLike);
    let likeUpdate = blog.likes

    isLike === false ? (likeUpdate > 0 ? likeUpdate = blog.likes - 1 : likeUpdate = blog.likes)
      : likeUpdate = blog.likes + 1

    const putData = {
      'id': blog.id,
      "title": blog.title,
      "body": blog.body,
      "author": blog.author,
      "likes": likeUpdate,
    }
    console.log(likeUpdate)
    fetch(`http://localhost:3001/blogs/${id}`, {
      method: "PUT",
      body: JSON.stringify(putData),
      headers: {
        'Content-Type': 'application/json'
      },
    })
      .then(() => {
        navigate(`/blogs/${blog.id}`)

      })
      .catch(err => console.log(err))
    console.log('like!');
  }

  const counstLike = () => {
    console.log(isLike)
  }
  return (
    <div className="blog-details" >
      {isPending && <div>Loading...</div>}
      {error && <div>{error}</div>}
      {blog && (
        <article>
          <h2>{blog.title}</h2>
          <p>Written by {blog.author}</p>
          <div>{blog.body}</div>
          <button onClick={handleLikeClick}>
            {/* isLike에 의해 조건부 렌더링으로 빨간 하트(❤️)와 하얀 하트(🤍)가 번갈아 보여야 합니다. */}
            {!isLike ? '❤️' : '🤍'}
          </button>
          <button onClick={handleDeleteClick}>delete</button>
          <button onClick={counstLike}></button>
        </article>
      )}
    </div>
  );
}

export default BlogDetails; 



CreateBlog.js

CreateBlog.js에서는 앞에서와 같이 등록 버튼을 누르면 게시물이 등록이 되며 home으로 리다이렉트 되도록 useNavigate()를 이용하여 작성 하였다.

useInput이라는 Hook을 작성하여 입력 값에 대한 State를 컨트롤 했다.

input/textarea/select 태그까지 custom하고 싶었는데 다시 도전해봐야겠다.

import { useState } from "react";
import { useNavigate } from "react-router-dom";
import useInput from "../util/useInput";
const CreateBlog = () => {
  const [title, onChangeText] = useInput('');
  const [body, onChangeBody] = useInput('');
  const [author, onChangeAuthor] = useInput('김코딩');
  const navigate = useNavigate();
  const handleSubmit = (e) => {
    e.preventDefault();
    console.log(e.type);
    /* 등록 버튼을 누르면 게시물이 등록이 되며 home으로 리다이렉트 되어야 합니다. */
    /* 작성한 내용과 useNavigate를 이용하여 handleSubmit의 로직을 작성해보세요. */
    const data = { title, body, author, likes: 0 }
    fetch('http://localhost:3001/blogs/', {
      method: "POST",
      headers: { "Content-type": "Application/json" },
      body: JSON.stringify(data)
    })
      .then(res => {
        if (!res.ok) {
          throw Error('could not fetch the data for that resource', {
            method: "DELETE"
          });
        }
        return res.json();
      })
      .then(() => {
        navigate('/')
        window.location.reload();
      })
      .catch(err => {
        console.error("Error", err);
      })
  }
  return (
    <div className="create">
      <h2>Add a New Blog</h2>
      <form onSubmit={handleSubmit}>
        <label>제목</label>
        <input
          type="text"
          required
          value={title}
          onChange={onChangeText}
          placeholder="제목을 입력해주세요."
        />
        <label>내용</label>
        <textarea
          required
          value={body}
          onChange={onChangeBody}
          placeholder="내용을 입력해주세요."
        ></textarea>
        <label>작성자</label>
        <select
          value={author}
          onChange={onChangeAuthor}
        >
          <option value="김코딩">김코딩</option>
          <option value="박해커">박해커</option>
        </select>
        <button>등록</button>
      </form>
    </div>
  );
}
export default CreateBlog; 



UseInput.js

CreateBlog.js 에서 사용한 Custom Hook

import { useState } from "react";

const useInput = (intialValue = "") => {
  const [text, setText] = useState(intialValue);

  const onChangeText = e => setText(e.target.value);

  return [text, onChangeText, setText];
}

export default useInput



UseFetch.js

GET 메소드를 통해 데이터를 받아오는 useEffect hook은 컴포넌트 내 여기저기 존재하고 있었지 때문에 UseFetch라는 Custom Hook을 작성해 주었다.

import { useState, useEffect } from 'react';

const useFetch = (url) => {
  /* useState를 이용하여 data, isPending, error를 정의하세요. */
  const [blogs, setBlogs] = useState(null);
  const [isPending, setIsPending] = useState(true);
  const [error, setError] = useState(null);

  /* useFetch 안의 중심 로직을 작성해주세요. */

  useEffect(() => {
    setTimeout(() => {
      fetch(url)
        .then(res => {
          if (!res.ok) {
            throw Error('could not fetch the data for that resource');
          }
          return res.json();
        })
        .then(data => {
          setIsPending(false);
          setBlogs(data)
          setError(null)
        })
        .catch(err => {
          setIsPending(false);
          setError(err.message)
        })
    }, 1000)
  }, [])

  return { blogs, isPending, error, }
}


export default useFetch;

결과물

0개의 댓글