코드를 짜기 전에 생각할 것
1. 전역적으로 사용하는 상태 생각하기
2. 상태를 변경하는 함수 생각하기
비회원일 경우
회원일 경우
디렉토리를 나누기
login된 상태와 login 되지 않은 상태 두가지로 나누어 <switch>
를 만들어주었다.
const App = () => {
const isLogin = false;
return (
<div>
<Header />
{isLogin ? (
<Switch>
<Route exact path="/" component={Home} />
<Route path="/mypage" component={MyPage} />
<Route path="/404" component={NotFound} />
<Redirect from="*" to="/404" />
</Switch>
) : (
<Switch>
<Route exact path="/" component={Login} />}
<Route path="/signup" component={SignUp} />
<Route path="/404" component={NotFound} />
<Redirect from="*" to="/404" />
</Switch>
)}
</div>
);
};
커스텀 hooks useInput 만들기
onReset에 사용할 setValue도 return 해준다.
import React, { useCallback, useState } from "react";
export default (initialValue) => {
const [value, setValue] = useState(initialValue);
const handler = useCallback((e) => {
const blank = /\s/;
if (blank.test(e.target.value) === true) {
alert("공백은 사용할 수 없습니다.");
return;
}
setValue(e.target.value);
}, []);
return [value, handler, setValue];
};
Login Page UI부분
const Login = () => {
const [id, onChangeId, setId] = useInput("");
const [pwd, onChangePwd, setPwd] = useInput("");
const onReset = useCallback(() => {
setId("");
setPwd("");
}, [setId, setPwd]);
const onLogin = () => {
if (!id || !pwd) {
alert("모든 값을 정확하게 입력해주세요");
return;
}
alert("로그인");
onReset();
};
return (
<Container>
<Title>PurpleCode</Title>
<form>
<InputContainer>
<InputItem>
<InputLabel htmlFor="user_id">아이디:</InputLabel>
<InputDiv>
<MyInput
id="user_id"
value={id}
onChange={onChangeId}
placeholder="아이디를 입력해주세요"
required
/>
<hr />
</InputDiv>
</InputItem>
<InputItem>
<InputLabel htmlFor="user_pwd">비밀번호:</InputLabel>
<InputDiv>
<MyInput
id="user_pwd"
value={pwd}
onChange={onChangePwd}
placeholder="비밀번호를 입력해주세요"
required
/>
<hr />
</InputDiv>
</InputItem>
</InputContainer>
</form>
<LoginBtn type="submit" value="로그인" onClick={onLogin}/>
<Link to="/signup">
<LoginBtn type="submit" value="회원가입" />
</Link>
</Container>
);
};
export default Login;
const SignUp = ({ history }) => {
const [id, onChangeId, setId] = useInput("");
const [pwd, onChangePwd, setPwd] = useInput("");
const [confirmPwd, onChangeConfirmPwd, setConfirmPwd] = useInput("");
const [errorMessage, setErrorMessage] = useState({
idError: "",
pwdError: "",
confirmPwdError: "",
});
const { idError, pwdError, confirmPwdError } = errorMessage;
const inputRegexs = {
idReg: /^[A-za-z0-9]{5,15}$/g,
pwdReg: /^(?=.*[A-Za-z])(?=.*\d)[A-Za-z\d]{8,}$/g,
};
const validationCheck = useCallback(
(input, regex) => {
let isValidate = false;
if (input === "") {
isValidate = false;
} else if (regex.test(input)) {
isValidate = true;
} else {
isValidate = false;
}
return isValidate;
},
[pwd, id]
);
const onReset = useCallback(() => {
setId("");
setPwd("");
setConfirmPwd("");
}, [setId, setPwd, setConfirmPwd]);
/* 아이디 체크 */
useEffect(() => {
if (validationCheck(id, inputRegexs.idReg) || id === "") {
setErrorMessage({
...errorMessage,
idError: "",
});
} else {
setErrorMessage({
...errorMessage,
idError: "아이디는 영문 또는 숫자로 5~15자 이여야 합니다.",
});
}
}, [id]);
/* 비밀번호 체크 */
useEffect(() => {
if (validationCheck(pwd, inputRegexs.pwdReg) || pwd === "") {
setErrorMessage({
...errorMessage,
pwdError: "",
});
} else {
setErrorMessage({
...errorMessage,
pwdError:
"비밀번호는 최소 하나의 문자 및 하나의 숫자로 8자 이상이여야 합니다.",
});
}
}, [pwd]);
/* 비밀번호 확인 체크 */
useEffect(() => {
if (pwd === confirmPwd || confirmPwd === "") {
setErrorMessage({
...errorMessage,
confirmPwdError: "",
});
} else {
setErrorMessage({
...errorMessage,
confirmPwdError: "비밀번호 확인이 일치하지 않습니다.",
});
}
}, [confirmPwd]);
const onSignUp = () => {
if (!id || !pwd || !confirmPwd) {
alert("모든 값을 정확하게 입력해주세요");
return;
}
if (idError) {
alert("아이디가 형식에 맞지 않습니다");
return;
} else if (pwdError) {
alert("비밀번호가 형식에 맞지 않습니다");
return;
} else if (confirmPwdError) {
alert("비밀번호 확인이 일치하지 않습니다.");
return;
}
alert("회원 가입 완료");
history.push("/");
onReset();
};
return (
<Container>
<Title>회원가입</Title>
<InputContainer>
<InputItem>
<InputTitle>아이디</InputTitle>
<Input
type="text"
placeholder="아이디를 입력하세요"
value={id}
onChange={onChangeId}
required
/>
{idError ? <ErrorMessage>{idError}</ErrorMessage> : ""}
</InputItem>
<InputItem>
<InputTitle>비밀번호</InputTitle>
<Input
type="password"
placeholder="비밀번호를 입력하세요"
value={pwd}
onChange={onChangePwd}
required
/>
{pwdError ? <ErrorMessage>{pwdError}</ErrorMessage> : ""}
</InputItem>
<InputItem>
<InputTitle>비밀번호 확인</InputTitle>
<Input
type="password"
placeholder="비밀번호 확인을 입력하세요"
value={confirmPwd}
onChange={onChangeConfirmPwd}
required
/>
{confirmPwdError ? (
<ErrorMessage>{confirmPwdError}</ErrorMessage>
) : (
""
)}
</InputItem>
</InputContainer>
<Btn type="submit" value="가입" onClick={onSignUp} />
<Link to="/">
<Btn type="submit" value="로그인" />
</Link>
</Container>
);
};
export default SignUp;
Context API를 사용하면 단 한 번에 원하는 값을 받아 와서 사용할 수 있습니다.
개념 정리
createContext 함수
를 사용해서 새 Context
를 만든다.
Provider
를 사용하면 context의 value
를 변경할 수 있다.
value에는 상태 값뿐만 아니라 함수도 전달해 줄 수 있다.
Context에 있는 값을 사용할 때 Consumer 대신 useContext
라는 Hook을 사용할 수 있다.
ex) const state = useContext(UserStateContext);
하면 value 값을 사용할 수 있다.
useReducer
: 첫 번째 파라미터에는 리듀서 함수를 넣고, 두 번째 파라미터에는 해당 리듀서의 기본값을 넣어 준다.
이 Hook을 사용하면 state
값과 dispatch
함수를 받아 옵니다.
state
는 현재 가리키고 있는 상태고, dispatch
는 액션 발생시키는 함수입니다. dispatch(action)
과 같은 형태로, 함수 안에 파라미터로 액션 값을 넣어 주면 리듀서 함수가 호출되는 구조입니다. 전역적으로 필요한 상태는 user
회원가입의 action type은 "CREATE_USER"
로그인은 "LOGIN" 로그아웃은 "LOGOUT"
"LOGIN" action
이 들어오면 user에 userID를 넣어준다.
"LOGOUT" action
이 들어오면 user를 다시 null로 만들어준다.
"CREATE_USER" action
이 들어오면 userList에 user를 추가해준다.
reducer
= 현재 상태, 그리고 업데이트를 위해 필요한 정보를 담은 action 값을 전달받아 새로운 상태를 반환하는 함수 !
UserContext.js 코드
import React, { createContext, useContext, useReducer } from "react";
const initialState = {
userList: [],
user: null,
};
const reducer = (state, action) => {
switch (action.type) {
case "CREATE_USER":
return {
...state,
userList: state.userList.concat(action.user),
};
case "LOGIN":
return {
...state,
user: {
userId: action.userId,
},
};
case "LOGOUT":
return {
...state,
user: null,
};
default:
return state;
}
};
const UserStateContext = createContext(null);
const UserDispatchContext = createContext(null);
export const UserProvider = ({ children }) => {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<UserStateContext.Provider value={state}>
<UserDispatchContext.Provider value={dispatch}>
{children}
</UserDispatchContext.Provider>
</UserStateContext.Provider>
);
};
export const useUserState = () => {
const state = useContext(UserStateContext);
if (!state) throw new Error("Cannot find UserProvider");
return state;
};
export const useUserDispatch = () => {
const dispatch = useContext(UserDispatchContext);
if (!dispatch) throw new Error("Cannot find UserProvider");
return dispatch;
};
index.js 코드
<UserProvider>
로 감싸준다.
import React from "react";
import ReactDOM from "react-dom";
import { BrowserRouter } from "react-router-dom";
import "./index.css";
import App from "./App";
import { UserProvider } from "./context/UserContext";
ReactDOM.render(
<UserProvider>
<BrowserRouter>
<App />
</BrowserRouter>
</UserProvider>,
document.getElementById("root")
);
App.js 코드
const isLogin = false;
을
const { user } = useUserState();
으로 변경
user가 있다면 회원의 Route로 없다면 비회원의 Route로 설정
dispatch
로 type "CREATE_USER"와 user객체에 id와 pwd 담아준다.
...
const signUp =({history})=>{
...
const dispatch = useUserDispatch();
...
onSignUp = () =>{
...
dispatch({
type: "CREATE_USER",
user: {
id,
pwd,
},
});
...
};
dispatch
에 type "LOGIN"과 userID에 id값을 담아준다.
const Login = () => {
...
const { userList } = useUserState();
const dispatch = useUserDispatch();
...
const onLogin = () => {
...
dispatch({
type: "LOGIN",
userId: id,
});
...
}
헤더를 만들어서
로고
와 id값
, 마이 페이지 버튼
, 로그아웃 버튼
을 보여주고,로고
와 회원가입페이지로 이동할 수 있는 버튼
을 보여주고, 로고
와 로그인페이지로 이동할 수 있는 버튼
을 보여주게 했다.로그아웃 구현
dispatch
에 type "LOGOUT"을 담아준다.
Header.js 코드
const Header = ({ location }) => {
const { user } = useUserState();
const dispatch = useUserDispatch();
const onLogOut = () => {
alert("로그아웃 되었습니다.");
dispatch({
type: "LOGOUT",
});
};
return (
<MainUl>
<Link to="/" style={{ textDecoration: "none" }}>
<LogoLi>PurpleCode</LogoLi>
</Link>
{user ? (
<NavDiv>
<MainLi>{user.userId}님</MainLi>
<MainLi>
<Link to="/mypage">
<SubmitInput type="submit" value="마이 페이지" />
</Link>
</MainLi>
<MainLi>
<SubmitInput type="submit" value="로그아웃" onClick={onLogOut} />
</MainLi>
</NavDiv>
) : location.pathname === "/signup" ? (
<NavDiv>
<MainLi>
<Link to="/">
<SubmitInput type="submit" value="로그인" />
</Link>
</MainLi>
</NavDiv>
) : (
<NavDiv>
<MainLi>
<Link to="/signup">
<SubmitInput type="submit" value="회원가입" />
</Link>
</MainLi>
</NavDiv>
)}
</MainUl>
);
};
export default withRouter(Header);
Home.js 코드
const Home = () => {
const { user } = useUserState();
return (
<div>
<MainP>{user.userId}님 환영합니다.</MainP>
</div>
);
};
[]
이고 user가 null
이다.{id:"annie" pwd:"..."}
가 추가 되었고, user는 null
이다.{userId: "annie"}
로 바뀌었다.useUserState
를 통해서 {user}를 조회한다.
MyPage.js 코드
const MyPage = () => {
const { user } = useUserState();
return (
<Container>
<Title>마이 페이지</Title>
<UserInfo>
<Id>아이디 : </Id>
<Value>
{user.userId}
<Line />
</Value>
</UserInfo>
<Link to="/mypage/modify">
<Btn type="submit" value="수정" />
</Link>
</Container>
);
};
UserContext에 action type "MODIFY"를 추가해서
dispatch
에 type "MODIFY"
, 지금 로그인한 id의 userList에서 index
값과 바꾼 id
, pwd
값을 담아주었다.
UserContext.js에 추가한 코드
case "MODIFY":
state.userList.splice(action.index, 1, {
id: action.userId,
pwd: action.userPwd,
});
return {
...state,
user: { userId: action.userId, userPwd: action.userPwd },
userList: state.userList,
};
Modify.js 코드
문제!!
Signup.js와 중복되는 부분 많다
const Modify = ({ history }) => {
const { user, userList } = useUserState();
const dispatch = useUserDispatch();
const [id, onChangeId] = useInput(user.userId);
const [nowPwd, onChangeNowPwd] = useInput("");
const [pwd, onChangePwd] = useInput("");
const [confirmPwd, onChangeConfirmPwd] = useInput("");
const [errorMessage, setErrorMessage] = useState({
idError: "",
pwdError: "",
confirmPwdError: "",
});
const { idError, pwdError, confirmPwdError, nowPwdError } = errorMessage;
const inputRegexs = {
idReg: /^[A-za-z0-9]{5,15}$/g,
pwdReg: /^(?=.*[A-Za-z])(?=.*\d)[A-Za-z\d]{8,}$/g,
};
const validationCheck = useCallback((input, regex) => {
let isValidate = false;
if (input === "") {
isValidate = false;
} else if (regex.test(input)) {
isValidate = true;
} else {
isValidate = false;
}
return isValidate;
}, []);
/* 아이디 체크 */
useEffect(() => {
const findUser = userList.find((user) => user.id === id);
if ((!findUser && validationCheck(id, inputRegexs.idReg)) || id === "") {
setErrorMessage({
...errorMessage,
idError: "",
});
} else if (findUser !== undefined) {
setErrorMessage({
...errorMessage,
idError: "이미 가입된 아이디입니다.",
});
} else if (!validationCheck(id, inputRegexs.idReg)) {
setErrorMessage({
...errorMessage,
idError: "아이디는 영문 또는 숫자로 5~15자 이여야 합니다.",
});
}
}, [id]);
/* 현재 비밀번호 체크 */
useEffect(() => {
if (user.userPwd === nowPwd || nowPwd === "") {
setErrorMessage({
...errorMessage,
nowPwdError: "",
});
} else {
setErrorMessage({
...errorMessage,
nowPwdError: "현재 비밀번호와 일치하지 않습니다.",
});
}
}, [nowPwd]);
/* 새 비밀번호 체크 */
useEffect(() => {
if (validationCheck(pwd, inputRegexs.pwdReg) || pwd === "") {
setErrorMessage({
...errorMessage,
pwdError: "",
});
} else {
setErrorMessage({
...errorMessage,
pwdError:
"비밀번호는 최소 하나의 문자 및 하나의 숫자로 8자 이상이여야 합니다.",
});
}
}, [pwd]);
/* 새 비밀번호 확인 체크 */
useEffect(() => {
if (pwd === confirmPwd || confirmPwd === "") {
setErrorMessage({
...errorMessage,
confirmPwdError: "",
});
} else {
setErrorMessage({
...errorMessage,
confirmPwdError: "비밀번호 확인이 일치하지 않습니다.",
});
}
}, [confirmPwd]);
const onModify = () => {
if (!id || !pwd || !confirmPwd || !nowPwd) {
alert("모든 값을 정확하게 입력해주세요");
return;
}
if (idError) {
alert("아이디가 형식에 맞지 않습니다");
return;
} else if (nowPwdError) {
alert("현재 비밀번호와 일치하지 않습니다.");
return;
} else if (pwdError) {
alert("비밀번호가 형식에 맞지 않습니다");
return;
} else if (confirmPwdError) {
alert("비밀번호 확인이 일치하지 않습니다.");
return;
}
const index = userList.findIndex((x) => x.id === user.userId);
dispatch({
type: "MODIFY",
index: index,
userId: id,
userPwd: pwd,
});
alert("수정을 완료했습니다.");
history.push("/mypage");
};
return (
<Container>
<Title>정보 수정</Title>
<UserInfo>
<InfoItem>
<InfoTitle>아이디 : </InfoTitle>
<Value type="text" value={id} onChange={onChangeId} />
</InfoItem>
{idError ? <ErrorMessage>{idError}</ErrorMessage> : ""}
<InfoItem>
<InfoTitle>현재 비밀번호 : </InfoTitle>
<Value type="password" value={nowPwd} onChange={onChangeNowPwd} />
</InfoItem>
{nowPwdError ? <ErrorMessage>{nowPwdError}</ErrorMessage> : ""}
<InfoItem>
<InfoTitle>새 비밀번호 : </InfoTitle>
<Value type="password" value={pwd} onChange={onChangePwd} />
</InfoItem>
{pwdError ? <ErrorMessage>{pwdError}</ErrorMessage> : ""}
<InfoItem>
<InfoTitle>새 비밀번호 확인: </InfoTitle>
<Value
type="password"
value={confirmPwd}
onChange={onChangeConfirmPwd}
/>
</InfoItem>
{confirmPwdError ? <ErrorMessage>{confirmPwdError}</ErrorMessage> : ""}
</UserInfo>
<Btn type="submit" value="수정 완료" onClick={onModify} />
</Container>
);
};
알맞게 수정한 뒤
user
와 userList
에서 test1이 annie로 바뀌었다