I created logged in form wherein it will pop-out a toastify when logged in correctly. So my problem right now is I have a logic wherein the user can only access the website when his account is active, but my toastify is not popping correctly. I'm using reduxjs/toolkit my state.
So what I'm trying to achieve here:
1.) Show an error message if the account is not existing or wrong password
2.) Show Account still pending if the user correctly input his username and password but still not active
3.) Navigate to login page when account is active
api/login.js
const login = async (req,res, next) =>{
try {
const user = await User.findOne({studentNumber: req.body.studentNumber})
if(!user) return next(createError(404, "User not found!"))
const isPasswordCorrect = await bcrypt.compare(req.body.password, user.password)
if(!isPasswordCorrect) return next(createError(400,"Wrong password or Username!"))
const accessToken = jwt.sign({id: user._id, isAdmin: user.isAdmin}, 'secretkey')
const {password, ...others} = user._doc
if(others.status === 'active'){
res.status(200).json({...others, accessToken})
}
else{
next(createError(203, "Account is still pending.."))
}
} catch (error) {
next(error);
}
}
authSlice.js
const authSlice = createSlice({
name: 'auth',
initialState: {
currentUser: null,
isFetching : false,
isError: false,
isSuccess: false,
message: ''
},
reducers: {
resetState: (state) =>{
state.currentUser = null;
state.isFetching = false
state.isError = false
state.isSuccess = false
},
loginStart: (state)=>{
state.isFetching = true;
state.isError = false;
},
loginSuccess: (state, action) =>{
state.isFetching = true;
state.isSuccess = true;
state.currentUser = action.payload
state.message = action.payload
},
loginFailure: (state, action) =>{
state.isSuccess = false;
state.isFetching = false;
state.isError = true;
state.message = action.payload
},
}
})
In authSlice.js, I have a problem that it automatically run the loginFailure, even though the user correctly input it's credential.
apiCall.js
const loginUser = async (user, dispatch) => {
dispatch(loginStart());
try {
const res = await publicRequest.post("/auth/login", user);
dispatch(loginSuccess(res.data));
} catch (err) {
dispatch(loginFailure());
}
};
Login.jsx
const {isError, currentUser, message} = useSelector((state) => state.auth)
const dispatch = useDispatch()
const navigate = useNavigate()
const {register, handleSubmit, formState: {errors}} = useForm({
studentNumber: '',
password: ''
})
const onSubmit = ({studentNumber,password}) =>{
let user ={studentNumber, password}
loginUser(user,dispatch)
}
useEffect(() =>{
if(isError){
toast.error(message)
}
dispatch(resetState())
},[dispatch, isError])
useEffect(() =>{
if(currentUser){
toast.success(message)
}
},[dispatch, currentUser])
Login.jsx
const Login () =>{
const {isError, currentUser, message} = useSelector((state) => state.auth)
const dispatch = useDispatch()
const navigate = useNavigate()
const {register, handleSubmit, formState: {errors}} = useForm({
studentNumber: '',
password: ''
})
const onSubmit = ({studentNumber,password}) =>{
let user ={studentNumber, password}
loginUser(user,dispatch)
}
useEffect(() =>{
if(isError){
toast.error(message)
}
dispatch(resetState())
},[dispatch, isError])
useEffect(() =>{
if(currentUser){
toast.success(message)
}
},[dispatch, currentUser])
return (
<form onSubmit={handleSubmit(onSubmit)}>
<TextField name="studentNumber" {...register('studentNumber)} type="text" />
<TextField name="password" {...register('password)} type="password" />
</form>
)
}
EDIT
Please ignore the form field if I type it wrong, I just copy paste it and missed something
In apiCall.js, don't just do dispatch(loginFailure());, instead inspect the response and if it has the 203 status code, dispatch the action differently. Maybe login failure, but with a payload that signals to your reducer that the user has logged in successfully, but their account is not active yet.
You probably also want to add another boolean flag to your state, something like state.auth.isActive, that is only true if the user account is not pending anymore. Or alternatively, if state.auth.currentUser is an object with more info, maybe the active/pending flag is contained in there already (in this case don't duplicate it).
Related
I am using redux login and after login want to show the first letter of the user in span and user login details(name or email) in my header section so please give the solution??
here is my redux login code
const authSlice = createSlice({
name: "auth",
initialState: {
user: null,
error: "",
loading: false,
},
extraReducers: {
[Login.pending]: (state, action) => {
state.loading = true;
},
[Login.fulfilled]: (state, action) => {
state.loading = false;
localStorage.setItem("user", JSON.stringify({ ...action.payload }))
state.user = action.payload
},
[Login.rejected]: (state, action) => {
state.loading = false;
state.error = action.payload.message;
},
[SignUp.pending]: (state, action) => {
state.loading = true;
},
[SignUp.fulfilled]: (state, action) => {
state.loading = false;
localStorage.setItem("user", JSON.stringify({ ...action.payload }));
state.user = action.payload;
},
[SignUp.rejected]: (state, action) => {
state.loading = false;
state.error = action.payload.message;
},
[Logout.pending]: (state, action) => {
state.loading = true;
},
[Logout.fulfilled]: (state, action) => {
state.loading = false;
localStorage.removeItem("user", JSON.stringify({ ...action.payload }));
state.user = action.payload;
},
[Logout.rejected]: (state, action) => {
state.loading = false;
state.error = action.payload.message;
}
}
});
here is my header code where enter details is the section where we have to enter the user login details to show the first letter of the user and name?
function Header() {
const {user} = useSelector((state) => state.auth);
const dispatch = useDispatch();
const navigate = useNavigate();
const logout = () =\> {
dispatch(Logout());;
navigate("/");
};
const htmlCode = () =\> {
return (
<span className="user-setting d-inline-block">
<h3 className="username">{enter details}</h3>
<span className="usericon">{enter details}</span>
</span>
)
}
```
I am trying to insert the {user.name} and other also but did not work??
Did you set up the store and the asyncthunk?
Is the reducer really named auth inside the store?
If everything is working you need to know what is going inside the user on the state, i will assume its an object like { name, email}, if that is the case then in the render you just have to do this { user.email } and to get the first letter use user.username.charAt(0)
============
Just to inform, useSelector re render the component based on the piece of state you select, if you will use just the user its better to use useSelector(state => state.auth.user ), that just matter if you have a complex component because react is fast
I am writing crud operations for my mern-stack using redux-toolkit. I want to prefill input fields with the data from the database. All that code works. I have the data in my input fields. But I am stopped by typescript, because TypeScript says:
The property X does not exist for the User[] type.
So how can I tell TypeScript that user has this properties? I tried it with exclamation marks and import the interface User into the userdisplay for setting user:User, but nothing helps.
The code in React:
const UserDisplay = () => {
//bringing in the user
const dispatch = useAppDispatch();
const selector = useAppSelector((state:RootState)=>state.user);
const {user, isError, isLoading, message} = selector;
const {id} = useParams();
useEffect(()=>{
if(isError){
toast.error(message);
}
dispatch(getUser(id!));
return ()=>{
dispatch(reset())
}
}, [dispatch, isError, message, id]);
//set Variables for input fields
const [formdata, setFormdata] = useState<{vorname:string,
nachname:string, username:string, email:string, street:string,
number:string,plz:string, city:string, isAdmin:boolean,
createdAt:string}>({
vorname:"",
nachname:"",
username:"",
email:"",
street:"",
number:"",
plz:"",
city:"",
isAdmin:false,
createdAt:Date.toLocaleString(),
})
console.log(user);
const {vorname, nachname, username, email, street, number, plz, city, isAdmin, createdAt} = formdata;
//overgive data from db to input fields
useEffect(()=>{
if(user){
setFormdata({
vorname:user.vorname, //here is every value underlined, property not exist
nachname:user.nachname,
username:user.username,
email:user.email,
street:user.street,
number: user.number,
plz:user.plz,
city:user.city,
isAdmin:user.isAdmin,
createdAt:user.createdAt,
})
}
}, [user])
That are the parts out of the slice:
interface User{
_id?:string,
id?:string,
vorname:string
nachname:string
username:string
email:string
street:string
number:string
plz:string
city:string
password:string
isAdmin:boolean
createdAt: Date
accessToken: string;
}
interface InitialState{
user:User[],
isLoading:boolean,
isSuccess:boolean,
isError:boolean,
message:string,
}
const initialState:InitialState ={
user:[],
isLoading:false,
isSuccess:false,
isError:false,
message:"",
}
type AsyncThunkConfig = {
state: RootState
}
//this function works - I get the user in my userDisplay
export const getUser = createAsyncThunk<User[], string, AsyncThunkConfig>('/user/find', async (Id:string, thunkAPI)=>{
try{
const token = thunkAPI.getState().auth.user!.accessToken;
return await userService.getUser(Id, token);
}catch (error:any) {
const message =
(error.response &&
error.response.data &&
error.response.data.message) ||
error.message ||
error.toString()
return thunkAPI.rejectWithValue(message as string)
}
})
Slice:
export const userSlice = createSlice({
name: 'user',
initialState,
reducers:{
reset:(state)=>{
state.isLoading = false;
state.isSuccess = false;
state.isError = false;
state.message = "";
}
},
extraReducers(builder) {
builder
.addCase(updateUser.pending, (state)=>{
state.isLoading = true;
})
.addCase(updateUser.fulfilled, (state, action)=>{
state.isLoading = false;
state.isSuccess = true;
state.user.push(action.payload)
})
.addCase(updateUser.rejected, (state, action:any)=>{
state.isLoading = false;
state.isError = true;
state.message = action.payload;
})
.addCase(deleteUser.pending, (state)=>{
state.isLoading = true;
})
.addCase(deleteUser.fulfilled, (state, action)=>{
state.isLoading = false;
state.isSuccess = true;
state.user.filter((item)=>item._id !== action.payload.id)
})
.addCase(deleteUser.rejected, (state, action:any)=>{
state.isLoading = false;
state.isError = true;
state.message = action.payload;
})
.addCase(getUser.pending, (state)=>{
state.isLoading = true;
})
.addCase(getUser.fulfilled, (state, action)=>{
state.isLoading = false;
state.isSuccess = true;
state.user = action.payload; //that is underlined when I only use User or say object
})
I am working on the following tutorial: https://www.youtube.com/watch?v=UXjMo25Nnvc&list=PLillGF-RfqbbQeVSccR9PGKHzPJSWqcsm&index=4
Full GitHub project here from Brad Traversy: https://github.com/bradtraversy/mern-tutorial
For the Delete Goals section (starting at 37:06 to 44:56 timestamp)
Dashboard file: https://github.com/htqanh305/vocab-app/blob/main/frontend/src/pages/Dashboard.jsx
import {useEffect} from 'react'
import {useNavigate} from 'react-router-dom' // for redirecting
import {useSelector, useDispatch} from 'react-redux' // grab user to check state
import GoalForm from '../components/GoalForm'
import GoalItem from '../components/GoalItem'
import Spinner from '../components/Spinner'
import {getGoals, reset} from '../features/goals/goalSlice'
function Dashboard() {
const navigate = useNavigate()
const dispatch = useDispatch()
const {user} = useSelector((state) => state.auth)
const {goals, isLoading, isError, message} = useSelector((state) => state.goals)
useEffect(() => {
if(isError) {
console.log(message)
}
if(!user) {
navigate('/login')
}
dispatch(getGoals())
console.log("I reached this point")
return () => {
dispatch(reset())
}
}, [user, navigate, isError, message, dispatch])
if(isLoading) {
return <Spinner />
}
if(!user) {
navigate('/login')
} else {
return (
<>
<section className="heading">
<h1>Welcome {user.name} </h1>
<p>Goals Dashboard</p>
</section>
<GoalForm/>
<section className="content">
{goals.length > 0 ? (
<div className="goals">
{goals.map((goal) => (
<GoalItem key={goal._id} goal={goal} />
))}
</div>
) :
(<h3> You have not set any goals </h3>)}
</section>
</>
)
}
}
export default Dashboard
goalSlice file: https://github.com/bradtraversy/mern-tutorial/blob/main/frontend/src/features/goals/goalSlice.js
import {createSlice, createAsyncThunk} from '#reduxjs/toolkit'
import goalService from './goalService'
const initialState = {
goals: [],
isError: false,
isSuccess: false,
isLoading: false,
message: ''
}
// Create new goal
//thunkAPI object has a getState method that helps get anything we want/ any part of the state
// ie. get auth state to get token to access user so we can set/get goals
export const createGoal = createAsyncThunk('goals/create', async(goalData, thunkAPI) => {
try {
const token = thunkAPI.getState().auth.user.token // get token from outside of goal state (auth state)
return await goalService.createGoal(goalData, token)
} catch (error) {
const message = (error.response && error.response.data && error.response.data.message) || error.message || error.toString()
return thunkAPI.rejectWithValue(message)
}
} )
// Get user goals
export const getGoals = createAsyncThunk('goals/getAll', async (_, thunkAPI) => {
try {
const token = thunkAPI.getState().auth.user.token // get token from outside of goal state (auth state)
return await goalService.getGoals(token)
} catch (error) {
const message = (error.response && error.response.data && error.response.data.message) || error.message || error.toString()
return thunkAPI.rejectWithValue(message)
}
})
// Delete goal
export const deleteGoal = createAsyncThunk('goals/delete', async(id, thunkAPI) => {
try {
const token = thunkAPI.getState().auth.user.token // get token from outside of goal state (auth state)
return await goalService.deleteGoal(id, token)
} catch (error) {
const message = (error.response && error.response.data && error.response.data.message) || error.message || error.toString()
return thunkAPI.rejectWithValue(message)
}
} )
export const goalSlice = createSlice({
name: 'goal',
initialState,
reducers: {
reset: (state) => initialState
},
extraReducers: (builder) => {
builder
.addCase(createGoal.pending, (state) => {
state.isLoading = true
})
.addCase(createGoal.fulfilled, (state, action) => {
state.isLoading = false
state.isSuccess = true
state.goals.push(action.payload)
})
.addCase(createGoal.rejected, (state, action) => {
state.isLoading = false
state.isError = true
state.message = action.payload
})
.addCase(getGoals.pending, (state) => {
state.isLoading = true
})
.addCase(getGoals.fulfilled, (state, action) => {
state.isLoading = false
state.isSuccess = true
state.goals = action.payload
})
.addCase(getGoals.rejected, (state, action) => {
state.isLoading = false
state.isError = true
state.message = action.payload
})
.addCase(deleteGoal.pending, (state) => {
state.isLoading = true
})
.addCase(deleteGoal.fulfilled, (state, action) => {
state.isLoading = false
state.isSuccess = true
// filter out the UI when delete a goal, only show goals that are not deleted
console.log("confirm")
state.goals = state.goals.filter(
goal => goal._id !== action.payload.id)
console.log(action.payload.id)
console.log(state.goals)
})
.addCase(deleteGoal.rejected, (state, action) => {
state.isLoading = false
state.isError = true
state.message = action.payload
})
}
})
export const {reset} = goalSlice.actions
export default goalSlice.reducer
GoalItem file:
import {useDispatch} from 'react-redux'
import { deleteGoal } from '../features/goals/goalSlice'
function GoalItem({goal}) {
const dispatch = useDispatch()
return (
<div className="goal">
<div>
{new Date(goal.createdAt).toLocaleDateString('en-US')}
</div>
<h2>{goal.text}</h2>
<button onClick={() => dispatch(deleteGoal(goal._id))} className="close">X</button>
</div>
)
}
export default GoalItem
I'm following along in the tutorial, and no matter how much I try, I can't seem to get the delete goal functionality working.
When I create a new goal (item) and try to delete it, the item still remains on the page until I refresh or click on it twice. I'm not sure if it's something wrong with the goalSlice function or my Dashboard file, but for some reason it's not working.
I followed the tutorial to a tee, but it still looks like it isn't doing what it's supposed to.
Does anyone happen to know what is causing this Redux issue, or if I'm going crazy? Any suggestions or advice would be helpful. Thanks!
In my Login component I'm making two api requests. One is for authentication with JWT and another is getting data (which are some packs). They are getting called sequentially. I'm also using Redux to store the packs (response got from API call). For that I've set all the Actions and reducers. Note that I should get 4 objects in an array as response from the API call. To prevent auto logout on page refresh I've set the JWT token in localStorage and set that logic in the initialState of actions in Redux. But after logging in I'm getting an empty array as response. I'm also getting a 401 error for the second api call. But in login.js when I set the initialState to false I'm getting the response Here's what the response look like. But when I set the initialState based on localStorage it is giving me This 401 Error.
Login.js
function Login() {
const login = useSelector((state) => state.login);
const packs = useSelector((state) => state.packs);
const dispatch = useDispatch();
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const handleSubmit = async (e) => {
e.preventDefault();
const data = {
email: email,
password: password,
};
await Promise.all([
axios
.post(process.env.REACT_APP_LOGIN_URL_API, data)
.then((res) => localStorage.setItem("token", res.data.token))
.then(() => dispatch(setLogin()))
.catch(() => console.log("username or password do not match")),
axios
.get(process.env.REACT_APP_PACK_URL)
.then((res) => dispatch(getPacks(res.data.packs)))
.catch((err) => {
console.log(err);
}),
]);
};
console.log(packs);
if (login.isLogged) return <Redirect to="/" />;
...
login.js (reducer)
const initialState = {
isLogged: localStorage.getItem("token") ? true : false,
};
const login = (state = initialState, action) => {
switch (action.type) {
case "SET_LOGIN":
return {
isLogged: true,
};
default:
return state;
}
};
export default login;
packs.js (reducer)
const packs = (packs = [], action) => {
switch (action.type) {
case "FETCH_PACK":
return {
...packs,
packs: action.payload.packs,
};
default:
return packs;
}
};
export default packs;
actions.js
export const setLogin = () => ({
type: "SET_LOGIN",
payload: {
isLogged: true,
},
});
export const getPacks = (packs) => ({
type: "FETCH_PACK",
payload: {
packs: packs,
},
});
when I register it take me to the dashboard page where there is a welcome note with user name but the issue is I cannot see the user name until I refresh or login again. I am sharing both my registration action.js code and my dashboard
my registration.js code
const Register = (props) => {
const { classes } = props
const [name, setName] = useState('')
const [email, setEmail] = useState('')
const [password, setPassword] = useState('')
const dispatch = useDispatch();
const onRegister = async () => {
dispatch(startRegister(name, email, password));
props.history.push('/dashboard')
}
action.js
export const startRegister = (name, email, password) => {
// const { name, email, password } = props
return () => {
return firebase.auth().createUserWithEmailAndPassword(email, password)
.then((currentUser) => {
// Signed in
const user = firebase.auth().currentUser;
user.updateProfile({
displayName: name,
}).then(function () {
console.log('Updated');
// Update successful.
}).catch(function (error) {
// An error happened.
console.log(error);
});
// history.push('/dashboard')
// ...
})
.catch((error) => {
var errorMessage = error.message;
console.log(errorMessage);
// ..
});
};
};
dashboard.js
const Dashboard = props => {
const { classes } = props
const onSignout = async (e) => {
await firebase.auth().signOut()
//props.history.push('/')
}
const username = useSelector(state => state.auth.name);
return (
<main className={classes.main}>
<Paper className={classes.paper}>
<Typography component="h1" variant="h5">
{`Welcome User ${username ? username : ''}`}
At a glance I belive the issue is because of javascript promises, firebase.auth().createUserWithEmailAndPassword is an asynchronous function.
Try this
In registration.js
const onRegister = async () => {
const userData = {name, email, password}
dispatch(startRegister(props, userData));
}
In action.js
export const startRegister = (props, userData) => {
const { name, email, password } = userData;
return () => {
return firebase.auth().createUserWithEmailAndPassword(email, password)
.then((currentUser) => {
// Signed in
const user = firebase.auth().currentUser;
user.updateProfile({
displayName: name,
}).then(function () {
console.log('Updated');
// Navigate to dashboard after data is been saved
props.history.push('/dashboard');
}).catch(function (error) {
// An error happened.
console.log(error);
});
})
.catch((error) => {
var errorMessage = error.message;
console.log(errorMessage);
// ..
});
};
};