I'm trying to make a simple login/logout feature in my app using firebase auth rest API, I'm using redux to let user log in and logout, the user get registered perfectly in the firebase but when I hit Signup & Login button of the form, I'm getting this error 👇
With redux toolkit I'm wondering what's going wrong with my initialState of login function.
Here is my code: -
//login-store.js
const { configureStore, createSlice } = require("#reduxjs/toolkit");
const userAuth = createSlice({
name: "login",
initialState: {
token: "",
isLoggedIn: false,
login: (token) => {},
logout: () => {},
},
reducers: {
logginOut(state) {
state.isLoggedIn = false;
state.logout = () => {
localStorage.removeItem("userLoginToken");
};
},
loggingIn(state) {
state.isLoggedIn = true;
state.token = localStorage.getItem("userLoginToken");
state.login = (token) => {
return localStorage.setItem("userLoginToken", token);
};
},
},
});
const authStore = configureStore({
reducer: userAuth.reducer,
});
export const userAuthAction = userAuth.actions;
export default authStore;
And here I'm having my login and signup feature. Also there is one more problem, whenever I click New User button below the submit button, I immediately get the alert popup (written with comment below) I don't know how am I sending fetch request while switching the form...
//Login.js
import React, { useRef, useState } from "react";
import { useDispatch } from "react-redux";
import { useNavigate } from "react-router-dom";
import { userAuthAction } from "../store/login-store";
import classes from "./pages.module.css";
export default function Login() {
const dispatch = useDispatch();
const [isLogin, setIsLogin] = useState(true);
const navigate = useNavigate();
const emailInput = useRef();
const passwordInput = useRef();
const switchAuthTextHandler = () => {
setIsLogin((prevState) => !prevState);
};
const loginAuthHandler = (e) => {
e.preventDefault();
const enteredEmailValue = emailInput.current.value;
const enteredPasswordValue = passwordInput.current.value;
let authUrl;
if (isLogin) {
// when logging in
authUrl =
"https://identitytoolkit.googleapis.com/v1/accounts:signInWithPassword?key=AIzaSyB3Mbv38Ju8c9QedQzqX3QvufTCOXhkU0c";
} else {
// when signing up
authUrl =
"https://identitytoolkit.googleapis.com/v1/accounts:signUp?key=AIzaSyB3Mbv38Ju8c9QedQzqX3QvufTCOXhkU0c";
}
fetch(authUrl, {
method: "POST",
body: JSON.stringify({
email: enteredEmailValue,
password: enteredPasswordValue,
returnSecureToken: true,
}),
headers: {
"Content-type": "application/json",
},
})
.then((res) => {
if (res.ok) {
return res.json();
} else {
return res.json().then((data) => {
// getting alert popup immediately after switching the form
alert(data.error.message);
});
}
})
.then((data) => {
dispatch(userAuthAction.loggingIn(data.idToken));
navigate("/");
console.log(data);
})
.catch((err) => {
console.error(err.message);
});
};
return (
<div className={classes.loginWrapper}>
<form onSubmit={loginAuthHandler}>
<h4>{isLogin ? "Login" : "Signup"}</h4>
<div className={classes.form_group}>
<label htmlFor="email">Email</label>
<input type="email" id="email" ref={emailInput} />
</div>
<div className={classes.form_group}>
<label htmlFor="password">Password</label>
<input type="password" id="password" ref={passwordInput} />
</div>
<div className={classes.form_group}>
<button type="submit">{isLogin ? "Login" : "Signup"}</button>
</div>
<div className={classes.form_group}>
<button className={classes.newUser} onClick={switchAuthTextHandler}>
{isLogin ? "New User?" : "Already have account"}
</button>
</div>
</form>
</div>
);
}
Error while siging up the new user:-
We should not store function references in the redux store. They are not serializable, and states should be serializable in redux state.
Related
i want to implement update user password form using react-router-dom v6 but this code is not working..
please please.. put your suggestion or explain me about my mistakes on this code.
userReducer.js
import {
UPDATE_PASSWORD_REQUEST,
UPDATE_PASSWORD_SUCCESS,
UPDATE_PASSWORD_RESET,
UPDATE_PASSWORD_FAIL,
CLEAR_ERRORS,
} from "../Constants/userConstant";
export const profileReducer = (state = {}, action) => {
switch (action.type) {
case UPDATE_PASSWORD_REQUEST:
return {
...state,
loading: true,
};
case UPDATE_PASSWORD_SUCCESS:
return {
...state,
loading: false,
isUpdated: action.payload,
};
case UPDATE_PASSWORD_FAIL:
return {
...state,
loading: false,
error: action.payload,
};
case UPDATE_PASSWORD_RESET:
return {
...state,
isUpdated: false,
};
case CLEAR_ERRORS:
return {
...state,
error: null,
};
default:
return state;
}
};
userAction.js
import {
UPDATE_PASSWORD_REQUEST,
UPDATE_PASSWORD_SUCCESS,
UPDATE_PASSWORD_FAIL,
CLEAR_ERRORS,
} from "../Constants/userConstant";
export const updatePassword = (passwords) => async (dispatch) => {
try {
dispatch({ type: UPDATE_PASSWORD_REQUEST });
const config = { headers: { "Content-Type": "application/json" } };
const { data } = await axios.put(
`/api/v1/password/update`,
passwords,
config
);
dispatch({ type: UPDATE_PASSWORD_SUCCESS, payload: data.success });
} catch (error) {
dispatch({
type: UPDATE_PASSWORD_FAIL,
payload: error.response.data.message,
});
}
};
export const clearErrors = () => async (dispatch) => {
dispatch({ type: CLEAR_ERRORS });
};
store.js
import {createStore,combineReducers,applyMiddleware} from 'redux';
import thunk from "redux-thunk";
import {composeWithDevTools} from "redux-devtools-extension";
import { profileReducer } from './Reducers/userReducer';
const reducer = combineReducers({
profile:profileReducer,
})
let initialState = {};
const middleware = [thunk];
const store = createStore(
reducer,
initialState,
composeWithDevTools(applyMiddleware(...middleware))
);
export default store;
UpdatePassword.js
import React, { Fragment, useState, useEffect } from "react";
import "./UpdatePassword.css";
import Loader from "../Loader/Loader";
import { useDispatch, useSelector } from "react-redux";
import { clearErrors, updatePassword } from "../../Actions/userAction";
import { UPDATE_PASSWORD_RESET } from "../../Constants/userConstant";
import {useNavigate} from 'react-router-dom'
const UpdatePassword = () => {
const dispatch = useDispatch();
const navigate = useNavigate();
const { error, isUpdated, loading } = useSelector((state) => state.profile);
const [oldPassword, setOldPassword] = useState("");
const [newPassword, setNewPassword] = useState("");
const [confirmPassword, setConfirmPassword] = useState("");
const updatePasswordSubmit = (e) => {
e.preventDefault();
const myForm = new FormData();
myForm.set("oldPassword", oldPassword);
myForm.set("newPassword", newPassword);
myForm.set("confirmPassword", confirmPassword);
dispatch(updatePassword(myForm));
};
useEffect(() => {
if (error) {
alert(error);
dispatch(clearErrors());
}
if (isUpdated) {
alert("Profile Updated Successfully");
navigate("/account");
dispatch({
type: UPDATE_PASSWORD_RESET,
});
}
}, [dispatch, error, isUpdated]);
return (
<Fragment>
{loading ? (
<Loader />
) : (
<Fragment>
{/* <MetaData title="Change Password" /> */}
<div className="updatePasswordContainer">
<div className="updatePasswordBox">
<h2 className="updatePasswordHeading">Update Profile</h2>
<form
className="updatePasswordForm"
onSubmit={updatePasswordSubmit}
>
<div className="loginPassword">
<input
type="password"
placeholder="Old Password"
required
value={oldPassword}
onChange={(e) => setOldPassword(e.target.value)}
/>
</div>
<div className="loginPassword">
<input
type="password"
placeholder="New Password"
required
value={newPassword}
onChange={(e) => setNewPassword(e.target.value)}
/>
</div>
<div className="loginPassword">
<input
type="password"
placeholder="Confirm Password"
required
value={confirmPassword}
onChange={(e) => setConfirmPassword(e.target.value)}
/>
</div>
<input
type="submit"
value="Change"
className="updatePasswordBtn"
/>
</form>
</div>
</div>
</Fragment>
)}
</Fragment>
);
};
export default UpdatePassword;
i want to make a form where user update user password.but due to any mistake this form is not working...
I would like to test (with RTL and jest) the api.post (with axios) triggered by a button in my react component bellow. It's about a login display with 2 inputs fields (email and password) and 2 buttons (enter and register). Both the enter and the register buttons called asynchronous functions.
// src > pages > Login
import React, { useState } from 'react';
import { useDispatch } from 'react-redux';
import { useNavigate } from 'react-router-dom';
import { saveToken } from '../app/slices/tokenSlice';
import { saveUserData } from '../app/slices/userSlice';
import api from '../services/api';
function Login() {
const [loginState, setLoginState] = useState({
email: '',
password: '',
});
const navigate = useNavigate();
const dispatch = useDispatch();
const handleChange = ({ target: { name, value } }) => {
setLoginState({
...loginState,
[name]: value,
});
};
const enterUser = async () => {
await api.post('/login', { ...loginState })
.then((response) => {
dispatch(saveToken(response.data.token));
dispatch(saveUserData(loginState));
navigate('/home');
})
.catch((error) => {
alert(error.message);
});
};
const registerUser = async () => {
api.post('/user', loginState)
.then(() => {
alert('Usuário cadastrado com sucesso!');
})
.catch((error) => {
alert(error.message);
});
};
return (
<div>
<label htmlFor="email">
Email:
<input
id="email"
name="email"
onChange={handleChange}
placeholder="Email"
type="text"
value={loginState.email}
/>
</label>
<label htmlFor="password">
Senha:
<input
id="password"
name="password"
onChange={handleChange}
type="password"
value={loginState.password}
/>
</label>
<button
name="btnEnterUser"
type="button"
onClick={() => enterUser()}
>
Entrar
</button>
<button
name="btnRegisterUser"
type="button"
onClick={() => registerUser()}
>
Cadastrar
</button>
</div>
);
}
export default Login;
// src > services > api.js
import axios from 'axios';
const { BASE_URL } = process.env;
const api = axios.create({
baseURL: BASE_URL,
});
api.defaults.headers.post['Content-Type'] = 'application/json;charset=utf-8';
api.defaults.headers.post['Access-Control-Allow-Origin'] = '*';
export default api;
How can i develop a test with mock data related to "registerUser" and "enterUser" functions. I have tried many options with jest.fn(), jest.spyOn() and userEvent.click(btnRegister), where btnRegister is the element which i getByRole as you can check in my test file bellow:
// src > tests > login.test.js
import React from 'react';
import '#testing-library/jest-dom';
import { screen } from '#testing-library/react';
import userEvent from '#testing-library/user-event';
import Login from '../pages/Login';
import renderWithRouter from './renderWithRouter';
import api from '../services/api';
describe('Componente Login', () => {
let inputEmail;
let inputPassword;
let btnRegister;
let btnEnter;
const userEmail = 'mariana#gmail.com';
const userPassword = '123456';
const tokenMock = {
token: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiI2MjA4MWYyYzBhMWFhNzFmYjhjZjU2NjAiLCJlbWFpbCI6Im1hcmlhbmFAZ21haWwuY29tIiwiaWF0IjoxNjQ1MzIyMjU1LCJleHAiOjE2NDUzNDAyNTV9.TIgJFIzg1W0bisvJ3CfRsVCZr3kbKn13_NBN-Ah1U1w',
};
const userRegisteredResponseMock = {
user:
{
email: 'vitao#gmail.com',
id: '6211a1d3eb25fc2418dec05a',
},
};
beforeAll(() => {
renderWithRouter(<Login />);
inputEmail = screen.getByLabelText(/email/i);
inputPassword = screen.getByLabelText(/senha/i);
btnRegister = screen.getByRole('button', { name: /cadastrar/i });
btnEnter = screen.getByRole('button', { name: /entrar/i });
userEvent.type(inputEmail, userEmail);
userEvent.type(inputPassword, userPassword);
});
it('Registro de usuário com sucesso', async () => {
// ************** DOESNT WORK **********************
// api.post = jest.fn().mockImplementation(() => {
// Promise.resolve(userRegisteredResponseMock);
// });
// api.post = jest.fn(() => Promise.resolve(userRegisteredResponseMock));
// api.post = jest.fn().mockResolvedValue(userRegisteredResponseMock);
// jest.spyOn(Login, 'enterUser');
// ****************************************************
userEvent.click(btnRegister);
expect(<"REGISTER USER" FUNCTION>).toBeCalledTimes(1);
});
});
I also have tried created "mocks" folder as jest documentation has mentioned in this link: https://jestjs.io/docs/manual-mocks , but without success.
in order to force jest using the mocked module, the jest.mock() function should be called.
import api from '../services/api';
...
jest.mock('../services/api');
...
api.post.mockResolvedValue(userRegisteredResponseMock);
it's also possible to mock axios module itself. furthermore, there's a jest-mock-axios npm module designed to achieve the behaviour.
Could someone please let me know why the state isn't being updated from the reducer? The useEffect(()=>{}) isn't being triggered when the state is being returned from the reducer. I have validated the correct information is being passed to the return, but nothing can be seen from the LoginScreen.
Context Script
import React, { createContext, useReducer } from "react";
import userReducer from "./UserReducer";
export const UserContext = createContext();
const initialState = {
userData: [],
isLoggedIn: false,
isAdmin: false,
isEmployee: false,
errorMessage: [{ success: false, statusCode: 0, error: null }],
};
const UserContextProvider = ({ children }) => {
const [state, dispatch] = useReducer(userReducer, initialState);
const registerUser = (user) =>
dispatch({ type: "REGISTER_USER", payload: user });
const loginUser = (user) => dispatch({ type: "LOGIN_USER", payload: user });
const deleteUser = (user) => dispatch({ type: "DELETE_USER", payload: user });
const updateUser = (user) => dispatch({ type: "UPDATE_USER", payload: user });
const contextValues = {
...state,
registerUser,
loginUser,
deleteUser,
updateUser,
};
return (
<UserContext.Provider value={contextValues}>
{children}
</UserContext.Provider>
);
};
export default UserContextProvider;
Reducer Script
import axios from "axios";
axios.defaults.withCredentials = true;
const userReducer = (state = {}, action) => {
let config = {
header: {
"Content-Type": "application/json",
},
};
switch (action.type) {
case "REGISTER_USER":
break;
case "LOGIN_USER":
console.log(state);
const email = action.payload.email;
const password = action.payload.password;
axios
.post("/api/user/login", { email, password }, config)
.then((response) => {
if (response.data.success) {
// localStorage.setItem("authToken", response.data.authToken);
state.userData = response.data.user;
state.isLoggedIn = true;
if (response.data.user.role === 9) {
state.isAdmin = true;
state.isEmployee = true;
} else {
state.isAdmin = false;
state.isEmployee = false;
}
}
})
.catch((error) => {
state.errorMessage = {
success: error.response.data.success,
statusCode: error.response.status,
message: error.response.data.error,
};
});
return {
...state,
userData: [state.userData],
isLoggedIn: state.isLoggedIn,
isAdmin: state.isAdmin,
isEmployee: state.isEmployee,
errorMessage: [state.errorMessage],
};
default:
return state;
}
};
export default userReducer;
Login Form
import { useState, useEffect, useContext } from "react";
import { Link } from "react-router-dom";
import {
Button,
Form,
Grid,
Message,
Segment,
Image,
Container,
} from "semantic-ui-react";
//Custom Imports
import "./LoginScreen.css";
import Logo from "../../../img/logo.png";
//Context
import { UserContext } from "../../context/UserContext";
const LoginScreen = ({ history }) => {
const { userData, loginUser, isLoggedIn, errorMessage, clearErrorMessage } =
useContext(UserContext);
const [user, setUser] = useState({ email: "", password: "" });
const [error, setError] = useState("");
useEffect(() => {
console.log(errorMessage);
if (localStorage.getItem("authToken")) {
history.push("/dashboard");
}
}, [history]);
useEffect(() => {
if (isLoggedIn) {
console.log(userData);
console.log("User is Logged in");
// history.push("/");
}
if (!errorMessage.success && errorMessage.error != null) {
console.log(errorMessage);
setError(errorMessage.message);
setTimeout(() => {
setError("");
}, 5000);
}
}, [userData, errorMessage, isLoggedIn]);
return (
<Container className="login-container">
<Grid
textAlign="center"
style={{ height: "100vh" }}
verticalAlign="middle"
>
<Grid.Column style={{ maxWidth: 450 }}>
<Image src={Logo} className="login-logo" />
<Form size="large" onSubmit={() => loginUser(user)}>
<Segment stacked>
<Form.Input
fluid
icon="user"
iconPosition="left"
placeholder="Email Address"
value={user.email}
onChange={(e) => setUser({ ...user, email: e.target.value })}
/>
<Form.Input
fluid
icon="lock"
iconPosition="left"
placeholder="Password"
value={user.password}
type="password"
onChange={(e) => setUser({ ...user, password: e.target.value })}
/>
{error && <span>{error}</span>}
<Button color="blue" fluid size="large" type="submit">
Login
</Button>
</Segment>
</Form>
<Message>
Don't have an account? <Link to="/register">Sign Up</Link>
</Message>
</Grid.Column>
</Grid>
</Container>
);
};
export default LoginScreen;
Refactor your login function like this
const loginUser({ email, password }) => {
let config = {
header: {
"Content-Type": "application/json",
},
};
axios
.post("/api/user/login", { email, password }, config)
.then((response) => {
if (response.data.success) {
dispatch({ type: 'LOGIN_SUCCESS', payload: response.data });
}
})
.catch((error) => {
dispatch({ type: 'LOGIN_FAILED', payload: error });
});
}
and then your reducer
...
switch(action.type) {
...
case 'LOGIN_SUCCESS':
// return here a new object
// do not mutate the state (state.something = something) is not allowed
...
case 'LOGIN_FAILED':
// handle error
}
Prerequisite Reducer Concepts
Redux and useReducer use reducer like (previousState, action) => newState.
The reducer should be a 'pure' function as in this document. The promises, api calls should not be use inside reducers.
The problem:
Because you call api/promise inside the reducer. The reducer function returns the value before the promise finish. So when the promise finishes, nothing happen.
// A will be return before B, C are going to call
case "LOGIN_USER":
promiseFn()
.then(/* B */ ...)
.catch(/* C */ ...)
// A
return {
...
}
Solution:
Separate the non-pure calls from the reducer. And put them in the other code blocks (like inside hooks, event handlers...).
I'm not sure if the problem is in useSelector or in useDispatch hooks or in another place, so here is the scenario:
Two screens (HomeScreen & AddBlogScreen)
In HomeScreen I click add blog button then it redirect to AddBlogScreen
I input the data, then submit. After the submit is success then redirect to HomeScreen
As mentioned in below pic, I got the no 4 result & I have to refresh to get the no 3 result. But my expectation is no 3 pic without getting the error.
Here is my code:
HomeScreen
import jwtDecode from "jwt-decode";
import React, { useEffect, useState } from "react";
import { useDispatch, useSelector } from "react-redux";
import { useHistory } from "react-router";
import { blogList } from "../redux/action";
export const MainScreen = () => {
// const [token, setToken] = useState(localStorage.getItem("token"));
const user = jwtDecode(localStorage.getItem("token"));
const history = useHistory();
const dispatch = useDispatch();
useEffect(() => {
dispatch(blogList());
}, [dispatch]);
const { blog } = useSelector((state) => state.blog);
console.log(blog);
return (
<>
<button
onClick={() => {
localStorage.removeItem("token");
history.push("/");
}}
>
singout
</button>
<button
onClick={() => {
history.push({ pathname: "/Blog", state: user });
}}
>
add blog
</button>
<h1 style={{ color: "red" }}>username: {user.username}</h1>
{blog.map(({ id, b_title, b_content, category_id }) => (
<div key={id}>
<h1
onClick={() =>
history.push({
pathname: "/Edit",
state: { id, b_title, b_content, category_id },
})
}
>
Title: {b_title}
</h1>
<p>Content: {b_content}</p>
</div>
))}
</>
);
};
AddBlogScreen
import React, { useState } from "react";
import { useDispatch } from "react-redux";
import { useHistory, useLocation } from "react-router";
import { addBlog } from "../redux/action";
export const AddBlogScreen = () => {
const history = useHistory();
const [title, setTitle] = useState("");
const [content, setContent] = useState("");
const [category, setCategory] = useState("");
const dispatch = useDispatch();
const location = useLocation();
const author = location.state.id;
const submitHandler = (e) => {
e.preventDefault();
dispatch(addBlog(title, content, author, category));
setTitle("");
setContent("");
setCategory("");
history.push("/Home");
};
return (
<div>
<h1>add blog page</h1>
<form onSubmit={submitHandler}>
<input
type="text"
placeholder="title"
value={title}
onChange={(e) => setTitle(e.target.value)}
/>
<br />
<br />
<input
type="text"
placeholder="content"
value={content}
onChange={(e) => setContent(e.target.value)}
/>
<br />
<br />
<input
type="text"
placeholder="category"
value={category}
onChange={(e) => setCategory(e.target.value)}
/>
<br />
<br />
<input
type="submit"
value="submit"
disabled={
title === "" || content === "" || category === "" ? true : false
}
/>
</form>
</div>
);
};
actions
import axios from "axios";
import {
LIST_BLOG,
ADD_BLOG,
EDIT_BLOG,
DELETE_BLOG,
LOGIN_USER,
REGISTER_USER,
LOGOUT_USER,
} from "./constant";
// ==================== blog actions ======================
export const blogList = () => async (dispatch) => {
try {
const result = await axios
.get("http://localhost:3001/api/v1/blog?page=0")
.then((res) => res.data.data)
.catch((err) => err);
dispatch({
type: LIST_BLOG,
payload: result,
});
} catch (err) {
dispatch({
payload: err,
});
}
};
export const addBlog =
(title, content, author, category) => async (dispatch) => {
try {
const result = await axios
.post("http://localhost:3001/api/v1/blog", {
blog_title: title,
blog_content: content,
author_id: author,
category_id: category,
})
.then(alert("success add blog"))
.catch((err) => alert(err));
dispatch({
type: ADD_BLOG,
payload: result,
});
} catch (err) {
dispatch({
payload: err,
});
}
};
reducer
const initial_state = {
blog: [],
};
export const blogReducer = (state = initial_state, action) => {
switch (action.type) {
case LIST_BLOG:
return {
...state,
blog: action.payload,
};
case ADD_BLOG:
return {
...state,
blog: action.payload,
};
case EDIT_BLOG:
return {
...state,
blog: action.payload,
};
case DELETE_BLOG:
return {
...state,
blog: action.payload,
};
default:
return state;
}
};
store
import { blogReducer, userReducer } from "./reducer";
import { combineReducers, createStore, applyMiddleware } from "redux";
import { composeWithDevTools } from "redux-devtools-extension";
import thunk from "redux-thunk";
const reducer = combineReducers({
blog: blogReducer,
user: userReducer,
});
const middleWare = composeWithDevTools(applyMiddleware(thunk));
export const store = createStore(reducer, middleWare);
First of all, the origin of error:
the error says a property named map on blog is not a function, meaning blog is not an array.
This is where it is coming from:
const { blog } = useSelector((state) => state.blog);
Your state is a an ojbect with a property named blog, you can access it these two ways:
const { blog } = useSelector((state) => state);
or
const blog = useSelector((state) => state.blog);
Other issues I noticed :
in addBlog:
1. When you are using try-catch with await, it's not a good idea to use then-catch too.
2.result won't be the blog data you expect. It will be an object, which is an instance of AxiosResponse, which includes the data.
you can extract the data from response object this way:
let response = await axios.post(... // some api request
let {data}=response
I would edit it like this:
export const addBlog =
(title, content, author, category) => async (dispatch) => {
try {
const {data} = await axios
.post("http://localhost:3001/api/v1/blog", {
blog_title: title,
blog_content: content,
author_id: author,
category_id: category,
})
alert("success add blog")
dispatch({
type: ADD_BLOG,
payload: data,
});
} catch (err) {
dispatch({
payload: err,
});
}
};
I found the solution, so in my action I changed it into:
dispatch({
type: LIST_BLOG,
payload: result.data.data,
});
I create a app, everything works fine, but when i move route or reload the page, the state back to the default situation, maybe i able to fix this problem using library as react-persist or found some way to put the state on the local Storage, i don't think this approach is the best way.Maybe you another alternative?
`store.js`
import { createStore, applyMiddleware } from "redux";
import thunk from "redux-thunk";
import rootReducer from "./reducer/rootReducer";
const initialState = {};
export default createStore(rootReducer, initialState, applyMiddleware(thunk));
`reducer`
import {
REGISTER_USER,
REGISTER_FAIL,
LOGIN_USER,
LOGIN_FAIL,
// LOAD_USER,
// LOAD_USER_FAIL,
// LOGOUT_USER,
} from "../constants";
const initialState = {
user: null,
loading: true,
isAuthenticated: false,
error: null,
};
export default (state = initialState, action) => {
switch (action.type) {
case REGISTER_USER:
return {
...state,
user: action.payload,
loading: false,
isAuthenticated: true,
error: null,
};
case REGISTER_FAIL:
return {
...state,
user: null,
loading: true,
isAuthenticated: false,
error: action.payload,
};
case LOGIN_USER:
localStorage.setItem("token", action.payload.token);
return {
...state,
user: action.payload,
loading: false,
isAuthenticated: true,
error: null,
};
case LOGIN_FAIL:
return {
...state,
user: null,
loading: true,
isAuthenticated: false,
error: action.payload,
};
default:
return state;
}
};
`Login`
import React, { useState, useEffect } from "react";
import { useDispatch, useSelector } from "react-redux";
import { Redirect, useHistory } from "react-router-dom";
import { loginAction } from "../actions/loginAction";
const Login = () => {
const dispatch = useDispatch();
const data = useSelector((state) => state.auth);
let history = useHistory();
const [msg, setMsg] = useState("");
const [state, setState] = useState({
email: "",
password: "",
});
useEffect(() => {
let interval;
if (data.error) {
setMsg(data.error.msg);
interval = setTimeout(() => {
setMsg("");
}, 1000);
}
if (data.user) {
setMsg(data.user.msg);
interval = setTimeout(() => {
setMsg("");
}, 1000);
}
return () => {
clearTimeout(interval);
};
}, [data]);
const handleSubmit = (e) => {
e.preventDefault();
dispatch(loginAction(state));
};
const handleChange = (e) => {
setState({ ...state, [e.target.name]: e.target.value });
};
if (data.isAuthenticated) {
history.push("/dashboard");
}
console.log(data);
return (
<>
<span>{msg}</span>
<h1>Login</h1>
<form onSubmit={handleSubmit}>
<input name="email" onChange={handleChange} value={state.email} />
<input name="password" onChange={handleChange} value={state.password} />
<input type="submit" value="Login" />
</form>
</>
);
};
export default Login;
register
import React, { useState, useEffect } from "react";
import { registerAction } from "../actions/registerAction";
import { useDispatch, useSelector } from "react-redux";
import { Redirect } from "react-router-dom";
const Register = () => {
const dispatch = useDispatch();
const data = useSelector((state) => state.auth);
const [msg, setMsg] = useState("");
const [state, setState] = useState({
name: "",
email: "",
password: "",
photo: "",
});
useEffect(() => {
let interval;
if (data.error) {
setMsg(data.error);
interval = setTimeout(() => {
setMsg("");
}, 1000);
}
if (data.user) {
setMsg(data.user.msg);
interval = setTimeout(() => {
setMsg("");
}, 1000);
}
return () => {
clearTimeout(interval);
};
}, [data]);
if (data.user) {
return <Redirect to="/login" />;
}
const handleSubmit = (e) => {
e.preventDefault();
const formData = new FormData();
formData.append("name", state.name);
formData.append("email", state.email);
formData.append("password", state.password);
formData.append("photo", state.photo);
dispatch(registerAction(formData));
};
const handleChange = (e) => {
setState({ ...state, [e.target.name]: e.target.value });
};
const handleOnUploadFile = (e) => {
setState({ ...state, photo: e.target.files[0] });
};
return (
<>
<span>{msg}</span>
<form onSubmit={handleSubmit}>
<label>Name</label>
<input name="name" onChange={handleChange} value={state.name} />
<label>Email</label>
<input name="email" onChange={handleChange} value={state.email} />
<label>Password</label>
<input name="password" onChange={handleChange} value={state.password} />
<input
name="photo"
type="file"
accept="image/*"
onChange={handleOnUploadFile}
/>
<input type="submit" />
</form>
</>
);
};
export default Register;
You should store a token in Local storage and pull it out and verify it upon reload. Also you should make a new action to load user from that token. States will alwayr reset to initial state on reload.
I cant see ur registerAction page but the loadUser should be something like this
// Load User
export const loadUser = () => async (dispatch) => {
try {
const res = await axios.get('/api/auth');
dispatch({
type: USER_LOADED,
payload: res.data,
} catch (err) {
dispatch({
type: AUTH_ERROR,
payload: err.response.data,
});
}
};
/api/auth is like this
// #route GET api/auth
// #desc Get logged in user (Login)
// #access Private
router.get('/', auth, async (req, res) => {
try {
const user = await Planner.findById(req.user.id).select('-password');
if (user.type === 'Admin') {
res.json({ user, admin: true });
} else {
res.json({ user, admin: false });
}
} catch (err) {
console.error(err.message);
res.status(500).send('Server Error');
}
});
You need to use jwttoken for example as an auth middle ware
the middleware file would be like this
const jwt = require('jsonwebtoken');
const config = require('config');
module.exports = function (req, res, next) {
//Get token from header
const token = req.header('x-auth-token');
// Check if not token
if (!token) {
return res.status(401).json({ msg: 'No token, authorization denied' });
}
try {
const decoded = jwt.verify(token, config.get('jwtSecret'));
req.user = decoded.user;
next();
} catch (err) {
res.status(401).json({ msg: 'Token is not valid' });
}
};
I have implemented a similar redux of the contact keeper project by Brad Travery
You can see the full code here am sure it will help
https://github.com/madaher-dev/contact-keeper
Enjoy