createUserWithEmailAndPassword
// SignUp.jsx
import { getAuth } from 'firebase/auth';
const SignUp = () => {
const navigate = useNavigate();
const [firebaseError, setFirebaseError] = useState("");
const auth = getAuth();
const handleSignupAndLogin = (email, password) => {
createUserWithEmailAndPassword(auth, email, password)
.then((user) => {
// 리덕스 스토어에 저장
navigate('/');
})
.catch(error => {
return error && setFirebaseError("이메일 또는 패스워드가 일치하지 않습니다.");
})
}
return (
<Form
title={"가입하기"}
getDataForm={handleSignupAndLogin}
firebaseError={firebaseError}
/>
)
}
// Form.jsx
const Form = ({title, getDataForm, firebaseError}) => {
const { register, handleSubmit, formState:{ errors }, reset } = useForm({
mode: 'onChange'
});
const onSubmit = ({email, password}) => {
getDataForm(email, password);
reset(); // input 비워줌
}
}
...
{firebaseError && <span>{firebaseError}</span>}
const SignIn = () => {
const navigate = useNavigate();
const [firebaseError, setFirebaseError] = useState("");
const auth = getAuth();
const handleLogin = (email, password) => {
signInWithEmailAndPassword(auth, email, password)
.then(user => {
navigate('/')
})
.catch(error => {
return error && setFirebaseError("이메일 또는 패스워드가 일치하지 않습니다.");
})
}
return (
<Form
title={"로그인"}
getDataForm={handleLogin}
firebaseError={firebaseError}
/>
)
}
store에 slice.js 파일 생성
/store/user/user.slice.js,
/store/order/order.slice.js,
/store/categories/categories.slice.js,
/store/cart/cart.slice.js,
/store/products/product.slice.js,
/store/products/products.slice.js 파일 생성.
메인 스토어 - index.js 생성
// store/index.js
export const store = configureStore({
reducer: {
userSlice,
... // 추후 추가
}
});
// store/user/user.slice.js
const initialState = localStorage.getItem("user")
? JSON.parse(localStorage.getItem("user") || "")
: {
email: "",
token: "",
id: ""
};
export const userSlice = createSlice({
name: "user",
initialState,
reducers: {
setUser: (state, action) => {
state.email = action.payload.email;
state.token = action.payload.token;
state.id = action.payload.id;
localStorage.setItem("user", JSON.stringify(state));
},
removeUser: (state) => {
state.email = "";
state.token = "";
state.id = "";
localStorage.setItem("user", JSON.stringify(state));
}
}
});
// 액션 생성자 (slice.actions) -> dispatch(actionName)
export const { setUser, removeUser } = userSlice.actions;
export default userSlice.reducer;
// main.jsx
import { Provider } from 'react-redux';
import { store } from './store';
...
<Provider store={store}>
<App />
</Provider>
// SignUp.jsx
import { setUser } from '../../store/user/user.slice';
import { getAuth } from 'firebase/auth';
const SignUp = () => {
const dispatch = useDispatch();
const navigate = useNavigate();
const [firebaseError, setFirebaseError] = useState("");
const auth = getAuth();
const handleSignupAndLogin = (email, password) => {
createUserWithEmailAndPassword(auth, email, password)
.then((userCredential) => {
// 리덕스 스토어에 저장
dispatch(setUser({
email: userCredential.user.email,
token: userCredential.user.refreshToken,
id: userCredential.user.uid
}))
navigate('/');
})
.catch(error => {
return error && setFirebaseError("이메일 또는 패스워드가 일치하지 않습니다.");
})
}
return (
<Form
title={"가입하기"}
getDataForm={handleSignupAndLogin}
firebaseError={firebaseError}
/>
);
}
지금까지 나는 한 페이지를 구성하는 컴포넌트들은 Components 폴더에 넣었는데,
여기서는 pages/HomePage 폴더 안에 폴더를 추가해서 하는 방식이다.
추가로, 페이지 컴포넌트 자체도 폴더로 구분해서 index.jsx로 생성하는 것이 차이.
const category = useSelector((state) => state.categoriesSlice);
// state에는 여러 slice들이 다 들어있음. (userSlice, categoriesSlice, ...)
const dispatch = useDispatch();
const getActiveCategory = () => {
dispatch(setActiveCategory(name)); // action.payload로 바로 전달함
}
import { useDispatch } form 'react-redux';
const useAppDispatch = () = useDispatch();
const useAppSelector = useSelector;
import { createAsyncThunk, createSlice } from "@reduxjs/toolkit";
import axios from "axios";
export const fetchProducts = createAsyncThunk(
"products/fetchProducts",
async (category: string, thunkAPI) => {
try {
let response;
if (category) {
response = await axios.get<IProduct[]>(`https://fakestoreapi.com/products/category/${category}`);
} else {
response = await axios.get<IProduct[]>("https://fakestoreapi.com/products");
}
return response.data; //payload
} catch (error) {
return thunkAPI.rejectWithValue("Error loading products");
}
}
)
...
const initialState: ProductsType = {
products: [],
isLoading: false,
error: "",
}
export const productsSlice = createSlice({
name: 'products',
initialState,
reducers: {},
extraReducers: (builder) => {
builder
.addCase(fetchProducts.pending, (state) => {
state.isLoading = true;
})
.addCase(fetchProducts.fulfilled, (state, action) => {
state.isLoading = false;
state.products = action.payload;
})
.addCase(fetchProducts.rejected, (state, action) => {
state.isLoading = false;
state.error = action.payload as string;
})
}
})
export default productsSlice.reducer;
import "react-loading-skeleton/dist/skeleton.css";
import Skeleton from 'react-loading-skeleton';
...
{ isLoading && <Skeleton height={350} /> }