[React : mini-project] CRUD(2) - Read List, Delete

문지은·2023년 7월 29일
0

React

목록 보기
17/24
post-thumbnail

List Read

DB에 있는 데이터를 불러와서 ListPage에 출력하는 것을 구현해보자.

  • axios get 요청을 통해 DB에 저장된 데이터를 불러올 수 있다.
  • ListPage에 아래와 같이 코드를 작성하고 console에 출력되는 데이터를 확인해보자.

src/pages/ListPage.jsx

import React from 'react'
import axios from 'axios'

function ListPage() {
  const getPosts = () => {
    axios.get('http://localhost:3001/posts')
    .then((response) => {
      console.log(response);
    })
  }

  getPosts();
  
  return (
    <div>ListPage</div>
  )
}

export default ListPage
  • 데이터를 잘 불러온 것을 확인할 수 있다.
  • useState를 사용하여 불러온 데이터를 state에 저장한다.
  • 무한 재렌더링을 막기 위해 useEffect를 사용하여 getPosts 함수를 한번만 실행하도록 한다.

src/components/ListPage.jsx

import React from 'react'
import axios from 'axios'
import { useState, useEffect } from 'react'

function ListPage() {
  const [posts, setPosts] = useState([]);

  const getPosts = () => {
    axios.get('http://localhost:3001/posts')
    .then((response) => {
      setPosts(response.data);
    })
  }

  useEffect(() => {
    getPosts();
  }, [])

  return (
    <div>ListPage</div>
  )
}

export default ListPage
  • map 함수를 사용해서 불러온 데이터를 출력한다.

src/components/ListPage.jsx

import React from 'react'
import axios from 'axios'
import { useState, useEffect } from 'react'

function ListPage() {
  const [posts, setPosts] = useState([]);

  const getPosts = () => {
    axios.get('http://localhost:3001/posts')
    .then((response) => {
      setPosts(response.data);
    })
  }

  useEffect(() => {
    getPosts();
  }, [])

  return (
    <div>
      <h1>Blogs</h1>
      {posts.map(post => {
        return (
          <div key={post.id}>{post.title}</div>
        )
      })}
    </div>
  )
}

export default ListPage
  • 실행 결과
    • DB에 저장된 데이터가 잘 출력되었다.

  • 부트스트랩 카드 컴포넌트를 이용하여 데이터를 카드 형태로 출력하도록 수정한다.

src/components/ListPage.jsx

import React from 'react'
import axios from 'axios'
import { useState, useEffect } from 'react'

function ListPage() {
  const [posts, setPosts] = useState([]);

  const getPosts = () => {
    axios.get('http://localhost:3001/posts')
    .then((response) => {
      setPosts(response.data);
    })
  }

  useEffect(() => {
    getPosts();
  }, [])

  return (
    <div>
      <h1>Blogs</h1>
      {posts.map(post => {
        return (
          <div className="card mb-3" key={post.id}>
            <div className="card-body">
              {post.title}
            </div>
          </div>
        )
      })}
    </div>
  )
}

export default ListPage

리팩토링 - Card 컴포넌트 만들기

props

  • Card 컴포넌트를 만들어 코드를 간결하게 만들고 재사용 할 수 있게 해보자.
  • post의 title을 Card 컴포넌트에 props로 전달하여 출력할 수 있도록 한다.

src/components/Card.jsx

import React from 'react'

function Card(props) {
  return (
    <div className="card mb-3">
        <div className="card-body">
            {props.title}
        </div>
    </div>
  )
}

export default Card
  • 원하는 값에 직접 접근도 가능
import React from 'react'

function Card({title}) {
  return (
    <div className="card mb-3">
        <div className="card-body">
            {title}
        </div>
    </div>
  )
}

export default Card

src/pages/ListPage.jsx

import React from 'react'
import axios from 'axios'
import { useState, useEffect } from 'react'

// Card 컴포넌트 불러오기
import Card from '../components/Card';


function ListPage() {
  const [posts, setPosts] = useState([]);

  const getPosts = () => {
    axios.get('http://localhost:3001/posts')
    .then((response) => {
      setPosts(response.data);
    })
  }

  useEffect(() => {
    getPosts();
  }, [])

  return (
    <div>
      <h1>Blogs</h1>
      {posts.map(post => {
        return (
          // <div className="card mb-3" key={post.id}>
          //   <div className="card-body">
          //     {post.title}
          //   </div>
          // </div>
          <Card key={post.id} title={post.title} />
        )
      })}
    </div>
  )
}

export default ListPage

children

  • title을 사용하지 않는 곳에서도 Card 컴포넌트를 사용할 수 있게 하기 위해 chilren 속성을 사용한다.
    • Card 컴포넌트 태그 안에 들어가는 내용이 props의 children이 된다.

src/pages/ListPage.jsx

import React from 'react'
import axios from 'axios'
import { useState, useEffect } from 'react'

import Card from '../components/Card';


function ListPage() {
  const [posts, setPosts] = useState([]);

  const getPosts = () => {
    axios.get('http://localhost:3001/posts')
    .then((response) => {
      setPosts(response.data);
    })
  }

  useEffect(() => {
    getPosts();
  }, [])

  return (
    <div>
      <h1>Blogs</h1>
      {posts.map(post => {
        return (
          <Card key={post.id} title={post.title}>
            <div className='d-flex justify-content-between'>
              <div>{post.title}</div>
              <div>buttons</div>
            </div>
          </Card>
        )
      })}
    </div>
  )
}

export default ListPage

src/components/Card.jsx

import React from 'react'

function Card({title, children}) {
  return (
    <div className="card mb-3">
        <div className="card-body">
            {children}
        </div>
    </div>
  )
}

export default Card

  • children과 props를 동시에 받아올 수도 있다.
  • Card 왼쪽에는 무조건 title을, 오른쪽에는 chilren을 출력하도록 코드 수정

src/pages/ListPage.jsx

import React from 'react'
import axios from 'axios'
import { useState, useEffect } from 'react'

import Card from '../components/Card';


function ListPage() {
  const [posts, setPosts] = useState([]);

  const getPosts = () => {
    axios.get('http://localhost:3001/posts')
    .then((response) => {
      setPosts(response.data);
    })
  }

  useEffect(() => {
    getPosts();
  }, [])

  return (
    <div>
      <h1>Blogs</h1>
      {posts.map(post => {
        return (
          <Card key={post.id} title={post.title}>
            <button>button</button>
          </Card>
        )
      })}
    </div>
  )
}

export default ListPage

src/components/Card.jsx

import React from 'react'

function Card({title, children}) {
  return (
    <div className="card mb-3">
        <div className="card-body">
            <div className='d-flex justify-content-between'>
                <div>{title}</div>
                {/* children이 있을 경우에만 출력 */}
                {children && <div>{children}</div>}
            </div>
        </div>
    </div>
  )
}

export default Card

prop types

  • 전달받은 props가 내가 원하는 props인지 확인할 수 있음
  • prop-types 설치하기
npm install --save prop-types
  • 전달할 title의 prop-type을 string으로 설정

src/components/Card.jsx

import React from 'react'

import PropTypes from 'prop-types'

function Card({title, children}) {
  return (
    <div className="card mb-3">
        <div className="card-body">
            <div className='d-flex justify-content-between'>
                <div>{title}</div>
                {/* children이 있을 경우에만 출력 */}
                {children && <div>{children}</div>}
            </div>
        </div>
    </div>
  )
}

Card.propTypes = {
    title: PropTypes.string,
}

export default Card
  • 만약 title로 string이 아닌 데이터를 전달할 경우 오류 메시지 출력
  • props의 기본 값을 설정할 수도 있음

src/components/Card.jsx

import React from 'react'

import PropTypes from 'prop-types'

function Card({title, children}) {
  return (
    <div className="card mb-3">
        <div className="card-body">
            <div className='d-flex justify-content-between'>
                <div>{title}</div>
                {/* children이 있을 경우에만 출력 */}
                {children && <div>{children}</div>}
            </div>
        </div>
    </div>
  )
}

Card.propTypes = {
    title: PropTypes.string,
}

Card.defaultProps = {
    title: 'Title'
}

export default Card
  • title로 아무 값도 넣어주지 않을 경우 기본 값으로 설정된 값 출력
  • 특정 prop을 필수 데이터로 지정할 수도 있음

src/components/Card.jsx

import React from 'react'

import PropTypes from 'prop-types'

function Card({title, children}) {
  return (
    <div className="card mb-3">
        <div className="card-body">
            <div className='d-flex justify-content-between'>
                <div>{title}</div>
                {/* children이 있을 경우에만 출력 */}
                {children && <div>{children}</div>}
            </div>
        </div>
    </div>
  )
}

Card.propTypes = {
    title: PropTypes.string.isRequired,
}

// Card.defaultProps = {
//     title: 'Title'
// }

export default Card
  • title로 아무 값도 넣어주지 않을 경우 오류 메시지 출력
  • children: PropTypes.element로 children 타입을 지정하면 children에는 한 개의 element만 올 수 있음

페이지 이동하기

포스트 생성 페이지로 이동하는 버튼

  • react router의 <Link> 컴포넌트 사용

src/pages/ListPage.jsx

import React from 'react'
import axios from 'axios'
import { useState, useEffect } from 'react'

import Card from '../components/Card';

// Link 컴포넌트 불러오기
import { Link } from 'react-router-dom';

function ListPage() {
  const [posts, setPosts] = useState([]);

  const getPosts = () => {
    axios.get('http://localhost:3001/posts')
    .then((response) => {
      setPosts(response.data);
    })
  }

  useEffect(() => {
    getPosts();
  }, [])

  return (
    <div>
      <div className='d-flex justify-content-between'>
        <h1>Blogs</h1>
        <div>
          // ********* 생성페이지로 이동하는 버튼
          <Link to="/blogs/create" className='btn btn-success'>Creat New</Link>
        </div>
      </div>
      {posts.map(post => {
        return (
          <Card key={post.id} title={post.title}>
            <button>button</button>
          </Card>
        )
      })}
    </div>
  )
}

export default ListPage

카드 컴포넌트 클릭시 수정 페이지로 이동

src/components/Card.jsx

  • Card를 클릭하면 실행할 onClick 함수를 props로 지정
    • type은 함수, 기본 값은 빈 함수로 정의
import React from 'react'

import PropTypes from 'prop-types'

function Card({title, onClick, children}) {
  return (
    <div className="card mb-3" onClick={onClick}>
        <div className="card-body">
            <div className='d-flex justify-content-between'>
                <div>{title}</div>
                {/* children이 있을 경우에만 출력 */}
                {children && <div>{children}</div>}
            </div>
        </div>
    </div>
  )
}

Card.propTypes = {
    title: PropTypes.string.isRequired,
    children: PropTypes.element,
    onClick: PropTypes.func
}

Card.defaultProps = {
    children: null,
    onClick: () => {}
}

export default Card

src/pages/ListPage.jsx

  • react router의 useNavigate 훅을 이용하여 수정 페이지로 이동하는 onClick 함수 작성
import React from 'react'
import axios from 'axios'
import { useState, useEffect } from 'react'

import Card from '../components/Card';

import { Link, useNavigate } from 'react-router-dom';

function ListPage() {
  const [posts, setPosts] = useState([]);
  
  const navigate = useNavigate();

  const getPosts = () => {
    axios.get('http://localhost:3001/posts')
    .then((response) => {
      setPosts(response.data);
    })
  }

  useEffect(() => {
    getPosts();
  }, [])

  return (
    <div>
      <div className='d-flex justify-content-between'>
        <h1>Blogs</h1>
        <div>
          <Link to="/blogs/create" className='btn btn-success'>Creat New</Link>
        </div>
      </div>
      {posts.map(post => {
        return (
          <Card 
            key={post.id} 
            title={post.title} 
            onClick={() => navigate("/blogs/edit")}>
            <button>button</button>
          </Card>
        )
      })}
    </div>
  )
}

export default ListPage
  • 카드 컴포넌트에 커서를 올리면 손가락 모양으로 바뀌도록 수정

src/index.css

.cursor-pointer {
  cursor: pointer;
}

src/components/Card.jsx

import React from 'react'

import PropTypes from 'prop-types'

function Card({title, onClick, children}) {
  return (
    <div className="card mb-3 cursor-pointer" onClick={onClick}>
        <div className="card-body">
            <div className='d-flex justify-content-between'>
                <div>{title}</div>
                {/* children이 있을 경우에만 출력 */}
                {children && <div>{children}</div>}
            </div>
        </div>
    </div>
  )
}

Card.propTypes = {
    title: PropTypes.string.isRequired,
    children: PropTypes.element,
    onClick: PropTypes.func
}

Card.defaultProps = {
    children: null,
    onClick: () => {}
}

export default Card

Delete

  • Delete 버튼 만들고 버튼 클릭 시 해당 포스트 DB에서 삭제하도록 하기

src/pages/ListPage.jsx

import React from 'react'
import axios from 'axios'
import { useState, useEffect } from 'react'

import Card from '../components/Card';

import { Link, useNavigate } from 'react-router-dom';

function ListPage() {
  const [posts, setPosts] = useState([]);

  const navigate = useNavigate();

  const getPosts = () => {
    axios.get('http://localhost:3001/posts')
    .then((response) => {
      setPosts(response.data);
    })
  }

  const deleteBlog = (event, id) => {
    // 이벤트 버블링 막기
    event.stopPropagation();
    axios.delete(`http://localhost:3001/posts/${id}`)
  }

  useEffect(() => {
    getPosts();
  }, [])

  return (
    <div>
      <div className='d-flex justify-content-between'>
        <h1>Blogs</h1>
        <div>
          <Link to="/blogs/create" className='btn btn-success'>Creat New</Link>
        </div>
      </div>
      {posts.map(post => {
        return (
          <Card 
            key={post.id} 
            title={post.title} 
            onClick={() => navigate("/blogs/edit")}>
            <button 
              className="btn btn-danger btn-sm" 
              onClick={(event) => deleteBlog(event, post.id)}>
              Delete</button>
          </Card>
        )
      })}
    </div>
  )
}

export default ListPage
  • 삭제버튼 클릭시 DB에서 해당 포스트가 삭제되지만 화면에는 새로고침 해야만 반영되는 것을 확인 할 수 있음
    • 삭제를 하더라도 posts state는 그대로 남아있기 때문!
  • 삭제 실행 후 posts state를 삭제하려는 id를 제외한 것들만 담는 과정을 수행하도록 코드 수정

src/pages/ListPage.jsx

import React from 'react'
import axios from 'axios'
import { useState, useEffect } from 'react'

import Card from '../components/Card';

import { Link, useNavigate } from 'react-router-dom';

function ListPage() {
  const [posts, setPosts] = useState([]);

  const navigate = useNavigate();

  const getPosts = () => {
    axios.get('http://localhost:3001/posts')
    .then((response) => {
      setPosts(response.data);
    })
  }

  const deleteBlog = (event, id) => {
    // 이벤트 버블링 막기
    event.stopPropagation();
    axios.delete(`http://localhost:3001/posts/${id}`)

    // ******** 삭제하려는 id와 같지 않은 post만 담아 posts 업데이트
    .then(() => {
      setPosts(prevPosts => {
        return prevPosts.filter(post => {
          return post.id !== id;
        })
      })
    })
  }

  useEffect(() => {
    getPosts();
  }, [])

  return (
    <div>
      <div className='d-flex justify-content-between'>
        <h1>Blogs</h1>
        <div>
          <Link to="/blogs/create" className='btn btn-success'>Creat New</Link>
        </div>
      </div>
      {posts.map(post => {
        return (
          <Card 
            key={post.id} 
            title={post.title} 
            onClick={() => navigate("/blogs/edit")}>
            <button 
              className="btn btn-danger btn-sm" 
              onClick={(event) => deleteBlog(event, post.id)}>
              Delete</button>
          </Card>
        )
      })}
    </div>
  )
}

export default ListPage
  • 정상 실행 확인

포스트가 없을 경우

  • 현재 포스트가 없을 경우 빈 화면 출력
  • 포스트가 DB에 존재하지 않으면 유저에게 텍스트로 알려주도록 수정해보자.

src/pages/ListPage.jsx

  • posts 길이가 0 이상일 때는 리스트 출력, 0 일 경우에는 'No blog posts found' 출력하도록 수정
import React from 'react'
import axios from 'axios'
import { useState, useEffect } from 'react'

import Card from '../components/Card';

import { Link, useNavigate } from 'react-router-dom';

function ListPage() {
  const [posts, setPosts] = useState([]);

  const navigate = useNavigate();

  const getPosts = () => {
    axios.get('http://localhost:3001/posts')
    .then((response) => {
      setPosts(response.data);
    })
  }

  const deleteBlog = (event, id) => {
    // 이벤트 버블링 막기
    event.stopPropagation();
    axios.delete(`http://localhost:3001/posts/${id}`)

    // 삭제하려는 id와 같지 않은 post만 담아 posts 업데이트
    .then(() => {
      setPosts(prevPosts => {
        return prevPosts.filter(post => {
          return post.id !== id;
        })
      })
    })
  }

  useEffect(() => {
    getPosts();
  }, [])

  return (
    <div>
      <div className='d-flex justify-content-between'>
        <h1>Blogs</h1>
        <div>
          <Link to="/blogs/create" className='btn btn-success'>Creat New</Link>
        </div>
      </div>
      // ****************************
      {posts.length > 0 ? posts.map(post => {
        return (
          <Card 
            key={post.id} 
            title={post.title} 
            onClick={() => navigate("/blogs/edit")}>
            <button 
              className="btn btn-danger btn-sm" 
              onClick={(event) => deleteBlog(event, post.id)}>
              Delete</button>
          </Card>
        )
      }) : 'No blog posts found'}
    </div>
  )
}

export default ListPage

Loading Spinner

  • 인터넷 속도가 느릴 경우 블로그 리스트를 불러오기 전에 No blog posts found 가 출력됨
  • 사용자 편의성을 위해 로딩 중에는 Loading Spinner를 출력하도록 수정해보자.
    • 부트스트랩 Spinner 컴포넌트 사용

src/pages/ListPage.jsx

  • 로딩중 상태를 저장할 state를 만들고, 기본값은 true로 정의
  • 로딩중일 때는 loading spinner를 출력하고 그렇지 않을 경우에만 post 출력하도록 작성
  • post 불러온 이후에는 loading 값을 false로 변경
import React from 'react'
import axios from 'axios'
import { useState, useEffect } from 'react'

import Card from '../components/Card';

import { Link, useNavigate } from 'react-router-dom';

function ListPage() {
  const [posts, setPosts] = useState([]);
  const navigate = useNavigate();
  
  // ******** 로딩중 상태를 저장할 state
  const [loading, setLoading] = useState(true);

  const getPosts = () => {
    axios.get('http://localhost:3001/posts')
    .then((response) => {
      setPosts(response.data);
      // post 불러온 이후에 로딩중 상태 false로 변경
      setLoading(false);
    })
  }

  const deleteBlog = (event, id) => {
    // 이벤트 버블링 막기
    event.stopPropagation();
    axios.delete(`http://localhost:3001/posts/${id}`)

    // 삭제하려는 id와 같지 않은 post만 담아 posts 업데이트
    .then(() => {
      setPosts(prevPosts => {
        return prevPosts.filter(post => {
          return post.id !== id;
        })
      })
    })
  }

  useEffect(() => {
    getPosts();
  }, [])

  return (
    <div>
      <div className='d-flex justify-content-between'>
        <h1>Blogs</h1>
        <div>
          <Link to="/blogs/create" className='btn btn-success'>Creat New</Link>
        </div>
      </div>
      // ******** 로딩중일 경우에는 spinner 출력
      { loading ? (
        <div className="d-flex justify-content-center">
          <div className="spinner-border" role="status">
            <span className="visually-hidden">Loading...</span>
          </div>
        </div>
      ) : posts.length > 0 ? posts.map(post => {
        return (
          <Card 
            key={post.id} 
            title={post.title} 
            onClick={() => navigate("/blogs/edit")}>
            <button 
              className="btn btn-danger btn-sm" 
              onClick={(event) => deleteBlog(event, post.id)}>
              Delete</button>
          </Card>
        )
      }) : 'No blog posts found'}
    </div>
  )
}

export default ListPage

코드 리팩토링

  • 삼항 연산자가 중첩 되어 가독성이 떨어지므로 post를 렌더링 하는 부분을 따로 빼서 조건문을 이용하여 같은 기능을 하는 코드를 작성해보자.
const renderBlogList = () => {
  // 로딩중일 경우 loading Spinner 출력
  if (loading) {
    return (
      <div className="d-flex justify-content-center">
        <div className="spinner-border" role="status">
          <span className="visually-hidden">Loading...</span>
        </div>
      </div>
    )
  }
  
  // post가 없을 경우 메시지 출력
  if (posts.length === 0) {
    return (<div>'No blog posts found'</div>)
  }
  
  // 나머지의 경우 post 출력
  return posts.map(post => {
    return (
      <Card 
        key={post.id} 
        title={post.title} 
        onClick={() => navigate("/blogs/edit")}>
        <button 
          className="btn btn-danger btn-sm" 
          onClick={(event) => deleteBlog(event, post.id)}>
          Delete</button>
      </Card>
    )
  })
}
  • ListPage.jsx 수정
import React from 'react'
import axios from 'axios'
import { useState, useEffect } from 'react'

import Card from '../components/Card';

import { Link, useNavigate } from 'react-router-dom';

function ListPage() {
  const [posts, setPosts] = useState([]);
  const navigate = useNavigate();

  const [loading, setLoading] = useState(true);

  const getPosts = () => {
    axios.get('http://localhost:3001/posts')
    .then((response) => {
      setPosts(response.data);
      // post 불러온 이후에 로딩중 상태 false로 변경
      setLoading(false);
    })
  }

  const deleteBlog = (event, id) => {
    // 이벤트 버블링 막기
    event.stopPropagation();
    axios.delete(`http://localhost:3001/posts/${id}`)

    // 삭제하려는 id와 같지 않은 post만 담아 posts 업데이트
    .then(() => {
      setPosts(prevPosts => {
        return prevPosts.filter(post => {
          return post.id !== id;
        })
      })
    })
  }

  useEffect(() => {
    getPosts();
  }, [])

  const renderBlogList = () => {
    if (loading) {
      return (
        <div className="d-flex justify-content-center">
          <div className="spinner-border" role="status">
            <span className="visually-hidden">Loading...</span>
          </div>
        </div>
      )
    }

    if (posts.length === 0) {
      return (<div>'No blog posts found'</div>)
    }

    return posts.map(post => {
      return (
        <Card 
          key={post.id} 
          title={post.title} 
          onClick={() => navigate("/blogs/edit")}>
          <button 
            className="btn btn-danger btn-sm" 
            onClick={(event) => deleteBlog(event, post.id)}>
            Delete</button>
        </Card>
      )
    })
  }
  return (
    <div>
      <div className='d-flex justify-content-between'>
        <h1>Blogs</h1>
        <div>
          <Link to="/blogs/create" className='btn btn-success'>Creat New</Link>
        </div>
      </div>
      // ****************************
      {renderBlogList()}
    </div>
  )
}

export default ListPage
  • Loading Spinner도 컴포넌트로 따로 작성하여 가독성과 재사용성을 높여보자.

src/componenets/LoadingSpinner.jsx

import React from 'react'

function LoadingSpinner() {
  return (
    <div className="d-flex justify-content-center">
        <div className="spinner-border" role="status">
        <span className="visually-hidden">Loading...</span>
        </div>
    </div>
  )
}

export default LoadingSpinner
  • ListPage.jsx 수정
import React from 'react'
import axios from 'axios'
import { useState, useEffect } from 'react'

import Card from '../components/Card';
import LoadingSpinner from '../components/LoadingSpinner';

import { Link, useNavigate } from 'react-router-dom';

function ListPage() {
  const [posts, setPosts] = useState([]);
  const navigate = useNavigate();

  const [loading, setLoading] = useState(true);

  const getPosts = () => {
    axios.get('http://localhost:3001/posts')
    .then((response) => {
      setPosts(response.data);
      // post 불러온 이후에 로딩중 상태 false로 변경
      setLoading(false);
    })
  }

  const deleteBlog = (event, id) => {
    // 이벤트 버블링 막기
    event.stopPropagation();
    axios.delete(`http://localhost:3001/posts/${id}`)

    // 삭제하려는 id와 같지 않은 post만 담아 posts 업데이트
    .then(() => {
      setPosts(prevPosts => {
        return prevPosts.filter(post => {
          return post.id !== id;
        })
      })
    })
  }

  useEffect(() => {
    getPosts();
  }, [])

  const renderBlogList = () => {
    if (loading) {
      return (
        // ****************************
        <LoadingSpinner/>
      )
    }

    if (posts.length === 0) {
      return (<div>'No blog posts found'</div>)
    }

    return posts.map(post => {
      return (
        <Card 
          key={post.id} 
          title={post.title} 
          onClick={() => navigate("/blogs/edit")}>
          <button 
            className="btn btn-danger btn-sm" 
            onClick={(event) => deleteBlog(event, post.id)}>
            Delete</button>
        </Card>
      )
    })
  }
  return (
    <div>
      <div className='d-flex justify-content-between'>
        <h1>Blogs</h1>
        <div>
          <Link to="/blogs/create" className='btn btn-success'>Creat New</Link>
        </div>
      </div>
      {renderBlogList()}
    </div>
  )
}

export default ListPage

Create 후 ListPage로 이동

  • react router의 useNavigate 훅 사용하여 create후 ListPage로 이동하도록 수정

src/components/BlogForm.jsx

import React from 'react'
import { useState } from 'react';
import axios from 'axios';
import { useNavigate } from 'react-router-dom';

function BlogForm() {
  const [title, setTitle] = useState('');
  const [body, setBody] = useState('');
  const navigate = useNavigate();

  const onSubmit = () => {
      axios.post('http://localhost:3001/posts', {
      title: title,
      body: body
      }).then(() => {
        // ******* create 후 list 페이지로 이동
        navigate('/blogs')
      })
  }
  return (
    <div>
        <h1>Create a blog post</h1>
        <div className='mb-3'>
            <label className='form-label'>Title</label>
            <input 
            className='form-control'
            value={title}
            onChange={(event) => {
                setTitle(event.target.value)
            }}
            />
        </div>
        <div className='mb-3'>
            <label className='form-label'>Body</label>
            <textarea 
            className='form-control'
            value={body}
            onChange={(event) => {
                setBody(event.target.value)
            }}
            rows={20}
            />
        </div>
        <button 
            className='btn btn-primary'
            onClick={onSubmit}
            >Post</button>
    </div>
  )
}

export default BlogForm

profile
코드로 꿈을 펼치는 개발자의 이야기, 노력과 열정이 가득한 곳 🌈

0개의 댓글