Skip to content
This repository was archived by the owner on Sep 19, 2021. It is now read-only.
14 changes: 7 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
# react-vote-11th

## 실행 방법
## 구현 방법

```
npm install
npm run dev
```
1. login form과 vote form을 로그인 결과에 따라 보여주는 페이지 index를 만든다.
2. 로그인시 서버에 리퀘스트를 보내 이메일과 비밀번호를 확인한다
3. vote form 내부 리스트 정렬을 vote list를 구현하여 진행한다

- npm install : 필요한 모든 패키지를 설치합니다. 처음 1번만 실행하면 됩니다.
- npm run dev : react(next) 웹서버를 localhost:3000에서 실행합니다.
## 어려웠던 점

개인적으로 시간을 너무 촉박하게 잡고 과제를 진행해서 아쉬운 부분이 많이 남습니다. 그리고 이상한 부분에서 계속 삽질을 해서,, 흑흑 시간이 조금 많이 모자랐던 것 같습니다. 서버 통신을 axios로 하는게 되게 간단하고 효율적인 것 같아 좋았습니다! memo는 어느 곳에 써야할지 아직은 감이 잘 안잡혀서 이부분도 더 공부하고 싶습니다.
31 changes: 31 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
"start": "node server"
},
"dependencies": {
"axios": "^0.19.2",
"compression": "^1.7.4",
"dotenv": "^8.2.0",
"express": "^4.17.1",
Expand Down
30 changes: 26 additions & 4 deletions pages/index.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,32 @@
import React from "react";
import React, { useState, useEffect } from "react";
import styled from "styled-components";
import axios from "axios";

import LoginForm from "../src/components/login-form";
import MemorizedLoginForm from "../src/components/login-form";
import MemorizedVoteForm from "../src/components/vote-form";

export default function Home() {
const [isLoggedIn, setLoginStatus] = useState(false);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const [isLoggedIn, setLoginStatus] = useState(false);
const [isLoggedIn, setIsLoggedIn] = useState(false);

useState()로 state를 선언할 때, 보통 그 state의 set 함수를 set 뒤에 state 이름을 붙여 네이밍 해줍니다. 그 state에 값을 set하는 함수임을 명시적으로 나타내기 위해서요!


const loginCheck = () => {
console.log("login check start");
if (isLoggedIn) {
console.log("dd");
return <MemorizedVoteForm />;
} else {
return (
<MemorizedLoginForm
isLoggedIn={isLoggedIn}
setLoginStatus={setLoginStatus}
/>
);
}
};

return (
<Wrapper>
리액트 투-표
<LoginForm />
<Title>리액트 투-표</Title>
{loginCheck()}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
{loginCheck()}
{!isLoggedIn && (<LoginForm/>)}
{isLoggedIn && (<VoteForm {...{setLoginStatus}}/>)}
  1. 컴포넌트들은 return() 안에서 확인할 수 있게 하면 좋아요. 굳이 컴포넌트를 리턴하는 함수를 만들지 말고 위와 같이 리팩토링하면 더 좋을 것 같아요 !
  2. console.log()는 commit할 때 다 지워주시는 게 좋아요!
  3. isLoggedIn<MemorizedLoginForm/>에 넘길 필요는 없을 것 같아요.
  4. props 이름과 넘길 값을 담고 있는 변수의 이름이 같다면 spread operator를 통해 props를 넘기면 좋아요!
  5. LoginForm, VoteForm에 모두 React.memo를 적용했지만 그렇다고 컴포넌트들을 import해서 쓸 때 Memoized를 붙여 import한 후 쓸 필요는 없어요. 참고

</Wrapper>
);
}
Expand All @@ -17,3 +36,6 @@ const Wrapper = styled.div`
padding: 10rem 40rem;
background-color: Azure;
`;
const Title = styled.h1`
font-size: 4rem;
`;
112 changes: 108 additions & 4 deletions src/components/login-form.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,118 @@
import React from "react";
import React, { useState, useEffect } from "react";
import styled from "styled-components";
import axios from "axios";

export default function LoginForm() {
return <Wrapper>안녕 나는 로그인 폼!</Wrapper>;
}
export default function LoginForm({ isLoggedIn, setLoginStatus, loginCheck }) {
const [form, setForm] = useState({ email: "", password: "" });

const handleFormChange = (e) => {
setForm({
...form,
[e.target.name]: e.target.value,
});
};
const initForm = () => {
setForm({ email: "", password: "" });
};
const validCheck = () => {
if (form.email.length === 0 || form.password.length === 0) {
alert("모든 항목을 입력하세요");
return false;
}
return true;
};
const handleSubmit = () => {
console.log(form);
if (!validCheck) return;
axios
.post(process.env.API_HOST + "/auth/signin/", form)
.then(function (response) {
console.log(response);
alert("로그인 성공!");
setLoginStatus(true);
console.log(isLoggedIn);
})
.catch(function (error) {
initForm();
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

멋쪄용 👍
함수 이름을 다음과 같이 변경하면 역할을 더 잘 파악할 수 있을 것 같아용
resetForm()

alert("로그인 실패!");
console.log(error);
});
};

return (
<Wrapper>
<LoginLabel>로그인</LoginLabel>
<InfoInputArea>
<EmailInputArea>
<EmailLabel>EMAIL</EmailLabel>
<EmailInput
name="email"
value={form.email}
onChange={handleFormChange}
/>
</EmailInputArea>
<PasswordInputArea>
<PasswordLabel>PASSWORD</PasswordLabel>
<PasswordInput
name="password"
value={form.password}
onChange={handleFormChange}
type="password"
/>
</PasswordInputArea>
</InfoInputArea>
<Submit onClick={handleSubmit}>로그인</Submit>
</Wrapper>
Comment on lines +43 to +65
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
<Wrapper>
<LoginLabel>로그인</LoginLabel>
<InfoInputArea>
<EmailInputArea>
<EmailLabel>EMAIL</EmailLabel>
<EmailInput
name="email"
value={form.email}
onChange={handleFormChange}
/>
</EmailInputArea>
<PasswordInputArea>
<PasswordLabel>PASSWORD</PasswordLabel>
<PasswordInput
name="password"
value={form.password}
onChange={handleFormChange}
type="password"
/>
</PasswordInputArea>
</InfoInputArea>
<Submit onClick={handleSubmit}>로그인</Submit>
</Wrapper>
<Wrapper>
<Title>로그인</Title>
<InputWrapper>
<Row>
<Label>EMAIL</Label>
<Input
name="email"
type="text"
value={form.email}
onChange={handleFormChange}
/>
</Row>
<Row>
<Label>PASSWORD</Label>
<Input
name="password"
type="password"
value={form.password}
onChange={handleFormChange}
/>
</Row>
</InputWrapper>
<SubmitButton onClick={handleSubmit}>로그인</SubmitButton>
</Wrapper>
  1. 컴포넌트 네이밍을 위와 같이 해보는 건 어떨까요? 컴포넌트들의 역할을 더 잘 파악할 수 있게욥
  2. <EmailInputArea/>, <PasswordInputArea/>, <EmailLabel/>, <PasswordLabel/>, <EmailInput/>, <PasswordInput/>은 style이 같기 때문에 굳이 컴포넌트를 따로 정의할 필요가 없어 보여요.
    Label, Input같은 경우 Label 안과 Input name을 통해 어떤 것에 대한 label과 input을 파악할 수 있어 컴포넌트 네이밍에 굳이 또 명시할 필요는 없어 보입니당.
  3. Input에는 type을 써주시는 게 좋아요 ㅎ
  4. 핸들러 네이밍 좋네요 !

);
}
export const MemoizedLoginForm = React.memo(LoginForm);
const Wrapper = styled.div`
width: 100%;
min-height: 30rem;
background-color: white;
font-size: 18px;
padding: 3rem 4rem;
display: flex;
flex-direction: column;
`;
const LoginLabel = styled.p`
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const LoginLabel = styled.p`
const LoginLabel = styled.label`

input의 label이기 때문에 p 태그 대신 label 태그를 써도 좋겠네용

font-weight: bold;
`;
const InfoInputArea = styled.div`
display: flex;
flex-direction: column;
justify-content: space-around;
`;
const EmailInputArea = styled.div`
display: flex;
flex-direction: row;
justify-content: space-between;
`;
const EmailInput = styled.input`
width: 50rem;
height: 3rem;
`;
const EmailLabel = styled.p`
font-size: 12px;
`;
const PasswordInput = styled.input`
width: 50rem;
height: 3rem;
`;
const PasswordInputArea = styled.div`
display: flex;
flex-direction: row;
justify-content: space-between;
`;
const PasswordLabel = styled.p`
font-size: 12px;
`;
const Submit = styled.button`
display: block;
margin-left: auto;
font-size: 1.8rem;
cursor: pointer;
padding: 0.5rem 1rem;
border: 1rem none;
outline: none;
`;
72 changes: 70 additions & 2 deletions src/components/vote-form.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,78 @@
import React from "react";
import React, { useState, useEffect } from "react";
import styled from "styled-components";
import axios from "axios";

import List from "./vote-list";
export default function VoteForm() {
return <Wrapper>안녕 나는 투표 폼!</Wrapper>;
const [candidates, setCandidateList] = useState([]);
useEffect(() => {
getCandidateList();
}, []);
const getCandidateList = async () => {
await axios
.get(process.env.API_HOST + "/candidates/", candidates)
.then(({ data }) => {
setCandidateList(data);
console.log(candidates);
})
.catch(function (error) {
console.log(error);
});
};
const Vote = (candidate) => {
axios
.put(
process.env.API_HOST + "/candidates/" + candidate.id + "/vote/",
voteCount
)
.then(({ data }) => {
alert(candidate.name + "님께 투표했습니다!");
getCandidates();
})
.catch(function (error) {
console.log(error);
alert("실패했습니다!");
});
};
let i = 1;
return (
<Wrapper>
<Title>
<RedTitle>프론트엔드 인기쟁이</RedTitle>는 누구?
</Title>
<Desc>CEOS 프론트엔드 개발자 인기순위 및 투표창입니다.</Desc>
<VoteSection>
{candidates
.sort((a, b) => {
return b.voteCount - a.voteCount;
})
.map((candidate) => (
<List
key={candidate._id}
id={candidate._id}
i={i++}
{...candidate}
getCandidateList={getCandidateList}
/>
))}
</VoteSection>
</Wrapper>
);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
let i = 1;
return (
<Wrapper>
<Title>
<RedTitle>프론트엔드 인기쟁이</RedTitle> 누구?
</Title>
<Desc>CEOS 프론트엔드 개발자 인기순위 및 투표창입니다.</Desc>
<VoteSection>
{candidates
.sort((a, b) => {
return b.voteCount - a.voteCount;
})
.map((candidate) => (
<List
key={candidate._id}
id={candidate._id}
i={i++}
{...candidate}
getCandidateList={getCandidateList}
/>
))}
</VoteSection>
</Wrapper>
);
return (
<Wrapper>
<Title>
<Red>프론트엔드 인기쟁이</Red> 누구?
</Title>
<SubTitle>CEOS 프론트엔드 개발자 인기순위 및 투표창입니다.</SubTitle>
<VoteSection>
{candidates
.sort((a, b) => {
return b.voteCount - a.voteCount;
})
.map((candidate, index) => {
const {_id : id} = candidate;
return(
<CandidateCard
key={id}
{...candidate}
{...{getCandidateList}}
/>
)})}
</VoteSection>
</Wrapper>
);
  1. 컴포넌트 이름들을 조금 더 명시적으로 바꿔봤어용
  2. candidate 객체 안에 _idid라는 이름으로 destructuring 해서 쓰면 더 좋을 것 같아용 참고
  3. indexmap()의 parameter에서 받아서 넘겨주면 됩니다.
  4. idcandidate 안에 있어 spread operator로 넘겨주면 id가 넘어가서 따로 또 넘겨줄 필요는 없어요!
  5. props 이름과 넘길 값을 담은 변수의 이름이 같다면 spread operator를 이용해 넘겨주면 좋아용
  6. <List/> 컴포넌트 네이밍이 아쉬워요. 여러 명에 대한 정보가 담겨있지 않으니 List는 아닙니당. <CandidateCard/>와 같이 네이밍 하는 건 어떨까용

}
export const MemoizedVoteForm = React.memo(VoteForm);

const Title = styled.h2``;
const RedTitle = styled.strong`
color: red;
`;
const Desc = styled.h3`
color: grey;
`;
const VoteSection = styled.div`
width: 100%;
padding: 5rem 10rem;
border: 1px solid black initial;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
border: 1px solid black initial;
border: 1px solid black;

border-image: initial은 스타일 구현에 필요 없는 코드이기 때문에 initial을 지워주시면 됩니당.

`;
const Wrapper = styled.div`
width: 100%;
min-height: 30rem;
Expand Down
55 changes: 55 additions & 0 deletions src/components/vote-list.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import React, { useState, useEffect } from "react";
import styled from "styled-components";
import axios from "axios";

export default function VoteList({ name, voteCount, id, i, getCandidateList }) {
const Vote = () => {
axios
.put(process.env.API_HOST + "/candidates/" + id + "/vote/", {
params: {},
})
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const Vote = () => {
axios
.put(process.env.API_HOST + "/candidates/" + id + "/vote/", {
params: {},
})
const handleVote = () => {
axios
.put(process.env.API_HOST + `/candidates/${id}/vote/`)
  1. 이벤트 핸들러의 이름을 정의할 때는 앞에 보통 handle을 붙여줍니다.
  2. API document 보시면 해당 엔드포인트로 데이터를 보낼 때 body 객체를 보낼 필요가 없음을 확인할 수 있습니다.

.then(function (response) {
console.log(response);
getCandidateList();
alert(name + "님께 투표 했습니다!");
})
.catch(function (error) {
console.log(error);
alert("투표 실패했습니다!");
});
};

return (
<Wrapper>
<Candidate>
<Rank>{i}위:</Rank> {name} [{voteCount}]표
</Candidate>
<VoteButton
onClick={() => {
Vote();
}}
>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
<VoteButton
onClick={() => {
Vote();
}}
>
<VoteButton
onClick={handleVote}
>

onClick에는 실행시킬 함수를 넣어주면 됩니다. handleVote()는 따로 매개변수를 받지 않기 때문에 위와 같이 코드 작성해주시면 됩니다.

투표
</VoteButton>
</Wrapper>
);
}
const Wrapper = styled.div`
width: 100%;
background-color: white;
font-size: 18px;
display: flex;
flex-direction: row;
align-items: center;
`;
const Rank = styled.strong``;
const Candidate = styled.div``;
const VoteButton = styled.button`
background-color: navy;
color: white;
font-size: 2rem;
padding: 0.5rem 1rem;
border-radius: 1rem;
display: block;
margin-left: auto;
`;