코드를 짜기 전에 생각할 것
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로 바뀌었다
