이번에는 인스타 내용물을 넣어보자
이전에 TopNav에 Outlet을 만들었는데 여기에도 주변에 OutletWrapper을 만들어서 출력해주어야 한다.
이걸 고치기 위해서 box-sizing은 border-box로 전역속성에 넣어줄 필요가 있다. 하는김에 다른 속성들도 모두 넣어주자.
import styled from "styled-components";
const PostList = () => {
return (
<List>
<Item>
<Container>
<Header>
<ProfileImage src="" />
<UserName>user</UserName>
</Header>
<Main>
<PostImage src="" />
<PostImage src="" />
</Main>
</Container>
</Item>
</List>
);
};
const List = styled.ul``;
const Item = styled.li``;
const Container = styled.article`
background: #fff;
border: 1px solid #dbdbdb;
margin-bottom: 24px;
`;
const Header = styled.header`
display: flex;
align-items: center;
padding: 15px;
`;
const ProfileImage = styled.img`
border-radius: 50%;
width: 32px;
height: 32px;
`;
const UserName = styled.span`
flex: 1px;
margin-left: 12px;
color: #262626;
`;
const Main = styled.main``;
const PostImage = styled.img`
width: 100%;
`;
export default PostList;
만들었으면 쿼리를 만들어야 한다.
일단 아무 이미지나 만들었다.
이제 Main에서 데이터를 가져올 수 있도록 만들어보자
위와같이 만들고 이제 데이터를 받아와보자
routes 폴더 아래에 posts.js 파일을 만들고 안에 위와같이 작성해주자 물론 아직 getPostMain이 없어서 만들어주어야 한다.
정상적으로 서버에서 데이터가 도착했다.
import conn from "../db/index.js";
export const getPostsMain = async (req, res) => {
const query = `
SELECT post.*, user.user_name, user.profile_image,
GROUP_CONCAT(image.url) AS imageList
FROM post
JOIN image ON image.post_id = post.id
JOIN user ON user.id = post.user_id
GROUP BY post.id
ORDER BY post.created_at DESC;
`;
const [rows] = await conn.query(query);
const postList = rows.map((post) => {
const imageList = post.imageList.split(",");
return { ...post, imageList };
});
res.send({ success: true, postList: postList });
};
위와같이 쿼리를 짜서 데이터를 받아오면 배열형태로 데이터가 돌아온다. 그것의 첫번째 데이터를 가져오자
결과 위와같이 리스트 형태로 이미지 데이터들이 무더기로 온것을 확인할 수 있다.
온 데이터들만 받아서 처리해보자
이걸 가지고 Main에서 처리해주어야 하는데 이걸 useEffect에서는 async-await를 사용할 수 없다. 그냥 함수를 빼기보다 IIFE를 써보자 IIFE는 즉시 실행함수이기 떄문에 async-await를 사용할 수 있다.
받아온 postList를 그냥 보내주면 렌더링이 안되기때문에 useState 안에 넣어서 보내주자.
다시금 PostList 에서는 데이터를 받아 화면에 뿌리는 역활을 수행해주자
import styled from "styled-components";
const PostList = ({ data }) => {
console.log(data);
return (
<List>
{data.map(({ id, content, imageList, profile_image, user_name }) => (
<Item key={id}>
<Container>
<Header>
<ProfileImage
src={
profile_image === null
? "https://www.luminous-edu.com/wp-content/uploads/2020/06/blank-profile-picture-973460_640.png"
: profile_image
}
/>
<UserName>{user_name}</UserName>
</Header>
<Main>
{imageList.map((image) => (
<PostImage key={image} src={image} />
))}
</Main>
</Container>
<Content>{content}</Content>
</Item>
))}
</List>
);
};
const List = styled.ul``;
const Item = styled.li``;
const Container = styled.article`
background: #fff;
border: 1px solid #dbdbdb;
margin-bottom: 24px;
`;
const Header = styled.header`
display: flex;
align-items: center;
padding: 15px;
`;
const ProfileImage = styled.img`
border-radius: 50%;
width: 32px;
height: 32px;
`;
const UserName = styled.span`
flex: 1px;
margin-left: 12px;
color: #262626;
`;
const Main = styled.main``;
const PostImage = styled.img`
width: 100%;
`;
const Content = styled.p``;
export default PostList;
짜긴했는데 문제점은 한번에 너무 많이 받아온다. 그러니 쿼리문에 제한을 두어서 그 수를 조절하자
posts controllers에 LIMIT 15를 두어서 가져오는 데이터의 수를 15개로 제한시켰다.
이제 파일을 업로드하기 위한 모달을 만들어보자
ModalAddPost를 마지막에 추가할껀데 이건 organisms에 추가할 예정이다.
ModalAddPost는 짜피 position:fixed 할것이기 때문에 이렇게 아래에 박아놓아도 괜찮다.
이렇게 먼저 만들고 모달 공통속성으로 atoms에 ModalContainer와 Bakdrop을 빼놓자
위와같이 화면이 가린것을 확인할 수 있다.
하지만 내용이 아래로 내려간 느낌이다. 구지 z-index를 넣지 말고 아래와같이 수정해서 모달을 앞으로 나오게끔 만들자
이제 제대로 나온다.
import React, { useState } from "react";
import { Link, Outlet } from "react-router-dom";
import styled from "styled-components";
import {
IconHome,
IconActivity,
IconDirect,
IconExplore,
IconNewPost,
} from "../../assets/images/icons/index";
import { ModalAddPost } from "./modals";
function TopNav() {
// 1. useState이용해서 평소에는 <ModalAddPost /> 안나옴
// 2. <IconNewPost /> 누르면 <ModalAddPost /> 나오게
// 3. <ModalAddPost />의 <Backdrop /> 누르면 <ModalAddPost /> 꺼지게
const [showModalAddPost, setShowModalAddPost] = useState(false);
return (
<div>
<Header>
<Main>
<Link to="/">
<ImgLogo src="https://www.instagram.com/static/images/web/mobile_nav_type_logo-2x.png/1b47f9d0e595.png" />
</Link>
<SearchWrapper>
<InputSearch plceholder="검색" />
</SearchWrapper>
<Nav>
<IconWrapper>
<IconHome />
</IconWrapper>
<IconWrapper>
<IconDirect />
</IconWrapper>
<IconWrapper>
<IconNewPost onClick={() => setShowModalAddPost(true)} />
</IconWrapper>
<IconWrapper>
<IconExplore />
</IconWrapper>
<IconWrapper>
<IconActivity />
</IconWrapper>
<IconWrapper>
<Link to="/Logout">Logout</Link>
</IconWrapper>
</Nav>
</Main>
</Header>
<OutletWrapper>
<Outlet />
</OutletWrapper>
{showModalAddPost && (
<ModalAddPost onClose={() => setShowModalAddPost(false)} />
)}
</div>
);
}
import React from "react";
import styled from "styled-components";
import { ModalContainer, Backdrop } from "../../atoms/modal";
const ModalAddPost = ({ onClose }) => {
return (
<>
<Backdrop onClick={onClose} />
<Container>
<Header>새 게시물 만들기</Header>
<Main>
<Guide>사진과 동영상을 여기에 끌어다 놓으세요</Guide>
<BtnFile>컴퓨터에서 선택</BtnFile>
<InputFile type="file" />
</Main>
</Container>
</>
);
};
하면 버튼 누를때 켜지고 Backdrop 누를떠 꺼진다.
문제점은 휠이 남아있어서 스크롤하면 게시글은 넘어간다. 이를 해결하기 위해 TopNav에 다음을 적어주자
없어진걸 확인할 수 있다.
useRef을 이용해서 직접 돔에 접근하자
InputFile을 숨기고
위와같이 useRef를 이용해서 태그들을 연결해주면 button으로도 input에서 가능했던 파일을 선택하는 기능을 만들 수 있다.
'Development(Web, Server, Cloud) > 22) React.js & Node.js' 카테고리의 다른 글
React.js & Node.js 22일차 (0) | 2022.01.27 |
---|---|
React.js & Node.js 21일차 (0) | 2022.01.27 |
React.js & Node.js 19일차 (0) | 2022.01.24 |
React.js & Node.js 18일차 - JWT, 회원가입, 로그인 구현 (0) | 2022.01.21 |
React.js & Node.js 17일차 : 인스타 디자인 (0) | 2022.01.21 |