dispatch updates only the first user on the database - javascript

i have a website where some users are localystored and stored in my mongodb, i created some buttons where when clicked they will firstly call the current state of the user to be the same as the on in the db, then after it should update the date value of the current localystored user. Whenever the button to update the date is clicked, for every user that is logged in, only the first user registered in the database is changed. I think the problem is in the backed update call or the Authcontext file but cant find a way to fix it.
File where the change should happen when function updateXMonth is called
import React, { useContext, useEffect } from "react";
import { AuthContext } from "../../context/AuthContext";
import { Navigate } from "react-router-dom";
import useFetch from "../../hooks/useFetch";
import { useState } from "react";
import Footer from "../../components/OutFooter";
import Navbar from "../../components/OutNavbar";
import Sidebar from "../../components/OutSidebar";
import {
ContractContainer,
HeadingContainer,
TypeH1,
ActiveUntil,
MonthlyWrapper,
MonthlyContainer,
MonthNumber,
Navbarback,
Desc,
TypeH2,
Subtitle,
} from "./userinfoElements";
import moment from "moment";
import axios from "axios";
import { Button } from "../../components/ButtonElements";
const Userinfo = () => {
// for nav bars
const [isOpen, setIsOpen] = useState(false);
// set state to true if false
const toggle = () => {
setIsOpen(!isOpen);
};
const { user, dispatch } = useContext(AuthContext);
const { data } = useFetch(`/contracts/${user?.contractType}`);
// useEffect(() => {
// if (user) {
// userUpdate();
// }
// });
let userUpdate = async () => {
try {
let res = await axios.post(`/auth/${user?.namekey}`);
dispatch({ type: "LOGIN_SUCCESS", payload: res.data.details });
let date = new Date().toJSON();
if (res.data.details.activeUntil < date) {
dispatch({ type: "CONTRACT_EXPIRED" });
console.log("Users contract has expired");
}
} catch (err) {
console.log(err);
}
};
if (!user) {
return <Navigate to="/" />;
}
let dateFormat = moment(user.activeUntil).format("DD/MMMM/yyyy");
const update1Month = async () => {
try {
let res = await axios.post(`/auth/${user.namekey}`);
dispatch({ type: "LOGIN_SUCCESS", payload: res.data.details });
let newDate = moment(res.data.details.activeUntil).add(30, "days");
dateFormat = newDate.format("DD/MMMM/yyyy");
await axios.put(`/activedate/${user.namekey}`, {
activeUntil: newDate,
});
dispatch({ type: "UPDATE_USER_DATE", payload: newDate });
} catch (err) {
console.log(err);
}
};
const update3Month = async () => {
try {
let res = await axios.post(`/auth/${user?.namekey}`);
dispatch({ type: "LOGIN_SUCCESS", payload: res.data.details });
let newDate = moment(res.data.details.activeUntil).add(90, "days");
dateFormat = newDate.format("DD/MMMM/yyyy");
await axios.put(`/activedate/${user.namekey}`, {
activeUntil: newDate,
});
dispatch({ type: "UPDATE_USER_DATE", payload: newDate });
} catch (err) {
console.log(err);
}
};
const update6Month = async () => {
try {
let res = await axios.post(`/auth/${user?.namekey}`);
dispatch({ type: "LOGIN_SUCCESS", payload: res.data.details });
let newDate = moment(res.data.details.activeUntil).add(180, "days");
dateFormat = newDate.format("DD/MMMM/yyyy");
await axios.put(`/activedate/${user.namekey}`, {
activeUntil: newDate,
});
dispatch({ type: "UPDATE_USER_DATE", payload: newDate });
} catch (err) {
console.log(err);
}
};
const update12Month = async () => {
try {
let res = await axios.post(`/auth/${user?.namekey}`);
dispatch({ type: "LOGIN_SUCCESS", payload: res.data.details });
let newDate = moment(res.data.details.activeUntil).add(365, "days");
dateFormat = newDate.format("DD/MMMM/yyyy");
await axios.put(`/activedate/${user.namekey}`, {
activeUntil: newDate,
});
dispatch({ type: "UPDATE_USER_DATE", payload: newDate });
} catch (err) {
console.log(err);
}
};
return (
<>
<Sidebar isOpen={isOpen} toggle={toggle} />
{/* navbar for smaller screens*/}
<Navbar toggle={toggle} />
<Navbarback /> {/* filling for transparent bacground navbar*/}
<>
<ContractContainer>
<TypeH1>
Hello {user.fName} {user.lName}!
</TypeH1>
<HeadingContainer>
<TypeH2>
Your contract type:{" "}
<span style={{ color: "red" }}>{data.contractType}</span>
</TypeH2>
<ActiveUntil>
Subscription active until{" "}
<span style={{ color: "red" }}>{dateFormat}</span>
</ActiveUntil>
</HeadingContainer>
<Subtitle>
Pay right now and get imediate access. The more the cheaper. If you
have a subscription already active, no days will be lost during the
proccess, they will only be added.
</Subtitle>
<MonthlyWrapper>
<MonthlyContainer>
<MonthNumber>1 Month</MonthNumber>
<Button onClick={update1Month}>{data.month1Price}$</Button>
<Desc>Pay right now to add 1 month to your subscription.</Desc>
</MonthlyContainer>
<MonthlyContainer>
<MonthNumber>3 Months</MonthNumber>
<Button onClick={update3Month}>{data.month3Price}$</Button>
<Desc>Pay right now to add 3 months to your subscription.</Desc>
</MonthlyContainer>
<MonthlyContainer>
<MonthNumber>6 Months</MonthNumber>
<Button onClick={update6Month}>{data.month6Price}$</Button>
<Desc>Pay right now to add 6 months to your subscription.</Desc>
</MonthlyContainer>
<MonthlyContainer>
<MonthNumber>12 Months</MonthNumber>
<Button onClick={update12Month}>{data.month12Price}$</Button>
<Desc>Pay right now to add 12 months to your subscription.</Desc>
</MonthlyContainer>
</MonthlyWrapper>
</ContractContainer>
</>
<Footer />
</>
);
};
export default Userinfo;
controller file for /activedate/
import User from "../models/User.js";
export const updateActiveDate = async (req, res, next) => {
try {
await User.updateOne({
$set: { activeUntil: req.body.activeUntil },
});
res.status(200).json("Active date has been updated.");
} catch (err) {
next(err);
}
};
AuthContext file
import React from "react";
import { createContext, useEffect, useReducer } from "react";
const INITIAL_STATE = {
user: JSON.parse(localStorage.getItem("user")) || null,
loading: false,
error: null,
expired: false,
};
export const AuthContext = createContext(INITIAL_STATE);
const AuthReducer = (state, action) => {
switch (action.type) {
case "LOGIN_START":
return {
user: null,
loading: true,
error: null,
expired: false,
};
case "LOGIN_SUCCESS":
return {
user: action.payload,
loading: false,
error: null,
expired: false,
};
case "CONTRACT_EXPIRED":
return {
user: null,
loading: false,
error: null,
expired: true,
};
case "LOGOUT":
return {
user: null,
loading: false,
error: null,
expired: false,
};
case "LOGIN_FAILURE":
return {
user: null,
loading: false,
error: action.payload,
expired: false,
};
case "UPDATE_USER_DATE":
const updatedUser = { ...state.user };
updatedUser.activeUntil = action.payload;
return {
...state,
user: updatedUser,
};
default:
return state;
}
};
export const AuthContextProvider = ({ children }) => {
const [state, dispatch] = useReducer(AuthReducer, INITIAL_STATE);
//update LStorage when user state updates
useEffect(() => {
localStorage.setItem("user", JSON.stringify(state.user));
}, [state.user]);
return (
<AuthContext.Provider
value={{
user: state.user,
loading: state.loading,
error: state.error,
expired: state.expired,
dispatch,
}}
>
{children}
</AuthContext.Provider>
);
};

Related

Code doesnt run after having 2 hooks with null properties

This part of the code is not working after I updated react scripts from 2.0 to 5.0.
const { user, dispatch } = useContext(AuthContext);
const { data } = useFetch(`/contracts/${user.contractType}`);
if (!user) {
return <Navigate to="/" />;
}
What I want to happen is if a user that isn't logged in tries to access this page via URL, they get redirected to the main website page.
The error that I get from the console:
TypeError: Cannot read properties of null (reading 'contractType')
at Userinfo (userinfo.js:25:1)
Here is the full code of the page
import React, { useContext } from "react";
import { useState } from "react";
import useFetch from "../../hooks/useFetch";
import Footer from "../../components/OutFooter";
import Navbar from "../../components/OutNavbar";
import Sidebar from "../../components/OutSidebar";
import {
ContractContainer,
HeadingContainer,
TypeH1,
ActiveUntil,
MonthlyWrapper,
MonthlyContainer,
MonthNumber,
Price,
Navbarback,
} from "./userinfoElements";
import { AuthContext } from "../../context/AuthContext";
import { Navigate } from "react-router-dom";
import moment from "moment";
import axios from "axios";
const Userinfo = () => {
const { user, dispatch } = useContext(AuthContext);
const { data } = useFetch(`/contracts/${user.contractType}`);
// for nav bars
const [isOpen, setIsOpen] = useState(false);
// set state to true if false
const toggle = () => {
setIsOpen(!isOpen);
};
if (!user) {
return <Navigate to="/" />;
}
let dateFormat = moment(user.activeUntil).format("DD/MMMM/yyyy");
const update1Month = async () => {
try {
let newDate = moment(user.activeUntil).add(30, "days");
dateFormat = newDate.format("DD/MMMM/yyyy");
await axios.put(`/activedate/${user.namekey}`, {
activeUntil: newDate,
});
dispatch({ type: "UPDATE_USER_DATE", payload: newDate });
} catch (err) {
console.log(err);
}
};
const update3Month = async () => {
try {
let newDate = moment(user.activeUntil).add(90, "days");
dateFormat = newDate.format("DD/MMMM/yyyy");
await axios.put(`/activedate/${user.namekey}`, {
activeUntil: newDate,
});
dispatch({ type: "UPDATE_USER_DATE", payload: newDate });
} catch (err) {
console.log(err);
}
};
const update6Month = async () => {
try {
let newDate = moment(user.activeUntil).add(180, "days");
dateFormat = newDate.format("DD/MMMM/yyyy");
await axios.put(`/activedate/${user.namekey}`, {
activeUntil: newDate,
});
dispatch({ type: "UPDATE_USER_DATE", payload: newDate });
} catch (err) {
console.log(err);
}
};
const update12Month = async () => {
try {
let newDate = moment(user.activeUntil).add(365, "days");
dateFormat = newDate.format("DD/MMMM/yyyy");
await axios.put(`/activedate/${user.namekey}`, {
activeUntil: newDate,
});
dispatch({ type: "UPDATE_USER_DATE", payload: newDate });
} catch (err) {
console.log(err);
}
};
return (
<>
<Sidebar isOpen={isOpen} toggle={toggle} />
{/* navbar for smaller screens*/}
<Navbar toggle={toggle} />
<Navbarback /> {/* filling for transparent bacground navbar*/}
<>
<ContractContainer>
<TypeH1>
Hello {user.fName} {user.lName}!
</TypeH1>
<HeadingContainer>
<TypeH1>{data.contractType}</TypeH1>
<ActiveUntil>Subscription active until {dateFormat}</ActiveUntil>
</HeadingContainer>
<MonthlyWrapper>
<MonthlyContainer>
<MonthNumber>1 Month</MonthNumber>
<Price onClick={update1Month}>{data.month1Price}$</Price>
</MonthlyContainer>
<MonthlyContainer>
<MonthNumber>3 Month</MonthNumber>
<Price onClick={update3Month}>{data.month3Price}$</Price>
</MonthlyContainer>
<MonthlyContainer>
<MonthNumber>6Month</MonthNumber>
<Price onClick={update6Month}>{data.month6Price}$</Price>
</MonthlyContainer>
<MonthlyContainer>
<MonthNumber>12Month</MonthNumber>
<Price onClick={update12Month}>{data.month12Price}$</Price>
</MonthlyContainer>
</MonthlyWrapper>
</ContractContainer>
</>
<Footer />
</>
);
};
export default Userinfo;
Addition:
The code works as I want it to when I add the if statement between the 2 hooks like this
const { user, dispatch } = useContext(AuthContext);
if (!user) {
return <Navigate to="/" />;
}
const { data } = useFetch(`/contracts/${user.contractType}`);
But then I get an error that react hook is being used conditionally. In the case before this where the if statement is used after the hooks, the code after const { data } = useFetch(/contracts/${user.contractType}); doesn't run (tried with console.log), is there a way I can make the statement at least get recognized after the useFetch hook is used? Or if not possible, is there a way to make it so that when a console error happens I can redirect to the main page or ignore the console error?
EDIT:
Adding files relevant to this.
AuthContext
import React from "react";
import { createContext, useEffect, useReducer } from "react";
const INITIAL_STATE = {
user: JSON.parse(localStorage.getItem("user")) || null,
loading: false,
error: null,
};
export const AuthContext = createContext(INITIAL_STATE);
const AuthReducer = (state, action) => {
switch (action.type) {
case "LOGIN_START":
return {
user: null,
loading: true,
error: null,
};
case "LOGIN_SUCCESS":
return {
user: action.payload,
loading: false,
error: null,
};
case "LOGIN_FAILURE":
return {
user: null,
loading: false,
error: action.payload,
};
case "LOGOUT":
return {
user: null,
loading: false,
error: null,
};
case "UPDATE_USER_DATE":
const updatedUser = { ...state.user };
updatedUser.activeUntil = action.payload;
return {
...state,
user: updatedUser,
};
default:
return state;
}
};
export const AuthContextProvider = ({ children }) => {
const [state, dispatch] = useReducer(AuthReducer, INITIAL_STATE);
useEffect(() => {
localStorage.setItem("user", JSON.stringify(state.user));
}, [state.user]);
return (
<AuthContext.Provider
value={{
user: state.user,
loading: state.loading,
error: state.error,
dispatch,
}}
>
{children}
</AuthContext.Provider>
);
};
useFetch
import { useEffect, useState } from "react";
import axios from "axios";
const useFetch = (url) => {
const [data, setData] = useState([]);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(false);
useEffect(() => {
const fetchData = async () => {
setLoading(true);
try {
const res = await axios.get(url);
setData(res.data);
} catch (err) {
setError(err);
}
setLoading(false);
};
fetchData();
}, [url]);
const reFetch = async () => {
setLoading(true);
try {
const res = await axios.get(url);
setData(res.data);
} catch (err) {
setError(err);
}
setLoading(false);
};
return { data, loading, error, reFetch };
};
export default useFetch;
API users mongoose schema
import mongoose from "mongoose";
const UserSchema = new mongoose.Schema({
fName: { type: String },
lName: { type: String },
namekey: {
type: String,
required: true,
unique: true,
},
password: {
type: String,
required: true,
},
contractType: {
type: String,
},
activeUntil: {
type: Date,
},
});
export default mongoose.model("User", UserSchema);
API contract mongoose schema
import mongoose from "mongoose";
const ContractsSchema = new mongoose.Schema({
contractType: {
type: String,
},
speed: {
type: Number,
},
month1Price: {
type: Number,
},
month3Price: {
type: Number,
},
month6Price: {
type: Number,
},
month12Price: {
type: Number,
},
promote: {
type: Boolean,
},
});
export default mongoose.model("Contracts", ContractsSchema);
API authentication of users
import User from "../models/User.js";
import bcrypt from "bcryptjs";
import { createError } from "../utils/error.js";
import jwt from "jsonwebtoken";
// export const register = async (req, res, next) => {
// try {
// const salt = bcrypt.genSaltSync(13);
// const hash = bcrypt.hashSync(req.body.password, salt);
// const newUser = new User({
// ...req.body,
// password: hash,
// });
// await newUser.save();
// res.status(200).send("User has been created.");
// } catch (err) {
// next(err);
// }
// };
export const login = async (req, res, next) => {
try {
const user = await User.findOne({ namekey: req.body.namekey });
if (!user) return next(createError(404, "User not found!"));
if (req.body.password === undefined) {
return next(createError(500, "Wrong password or namekey!"));
}
const isPasswordCorrect = await bcrypt.compare(
req.body.password,
user.password
);
if (!isPasswordCorrect)
return next(createError(400, "Wrong password or namekey!"));
const token = jwt.sign({ id: user._id }, process.env.JWT);
const { password, ...otherDetails } = user._doc;
res
.cookie("access_token", token, {
httpOnly: true,
})
.status(200)
.json({ details: { ...otherDetails } });
} catch (err) {
next(err);
}
};
App.js routes
import React from "react";
import "./App.css";
import { BrowserRouter, Routes, Route } from "react-router-dom";
import Home from "./pages/index";
import Signin from "./pages/signin/Signin";
import Userinfo from "./pages/userInfo/userinfo";
import PageNotFound from "./pages/PageNotFound";
function App() {
return (
<BrowserRouter>
<Routes>
<Route exact path="/" element={<Home />} />
<Route exact path="/signin" element={<Signin />} />
<Route exact path="/myinfo" element={<Userinfo />} />
<Route path="*" element={<PageNotFound />} />
</Routes>
</BrowserRouter>
);
}
export default App;
src index.js
import React from "react";
import ReactDOM from "react-dom/client";
import { AuthContextProvider } from "./context/AuthContext";
import App from "./App";
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
<React.StrictMode>
<AuthContextProvider>
<App />
</AuthContextProvider>
</React.StrictMode>
);
The initial user state is potentially null:
const INITIAL_STATE = {
user: JSON.parse(localStorage.getItem("user")) || null,
...
};
The AuthReducer functional also sets the user state to null in several cases:
const AuthReducer = (state, action) => {
switch (action.type) {
case "LOGIN_START":
return {
user: null, // <-- here
loading: true,
error: null,
};
case "LOGIN_SUCCESS":
return {
user: action.payload,
loading: false,
error: null,
};
case "LOGIN_FAILURE":
return {
user: null, // <-- here
loading: false,
error: action.payload,
};
case "LOGOUT":
return {
user: null, // <-- here
loading: false,
error: null,
};
case "UPDATE_USER_DATE":
const updatedUser = { ...state.user };
updatedUser.activeUntil = action.payload;
return {
...state,
user: updatedUser,
};
default:
return state;
}
};
With the existing code you should account for the potentially null user state object by using a null-check/guard-clause when passing the value to the useFetch hook.
Example:
const { user, dispatch } = useContext(AuthContext);
const { data, reFetch } = useFetch(`/contracts/${user?.contractType}`);
...
const useFetch = (url) => {
const [data, setData] = useState([]);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(false);
const const fetchData = React.useCallback(async () => {
setLoading(true);
try {
const res = await axios.get(url);
setData(res.data);
} catch (err) {
setError(err);
}
setLoading(false);
}, [url]);
useEffect(() => {
fetchData();
}, [fetchData]);
return { data, loading, error, reFetch: fetchData };
};

TypeError: Cannot destructure property 'user' of 'Object(...)(...)' as it is undefined

I'm relatively new to react. I'm trying to use the jwt login methods from a template I downloaded. It's throwing this error and I'm clueless any help would be appreciated.
AuthHooks.js
// ForJWT Auth
import {getUserFromJwtAuth} from './helper/AuthHelper';
import {
useJWTAuth,
useJWTAuthActions,
} from '../services/auth/jwt-auth/JWTAuthProvider';
export const useAuthUser = () => {
const {user, isAuthenticated, isLoading} = useJWTAuth();
return {
isLoading,
isAuthenticated,
user: getUserFromJwtAuth(user),
};
};
export const useAuthMethod = () => {
const {signInUser, signUpUser, logout} = useJWTAuthActions();
return {
signInUser,
logout,
signUpUser,
};
};
JWTAuthProvider.js
import React, {createContext, useContext, useEffect, useState} from 'react';
import PropTypes from 'prop-types';
import {useDispatch} from 'react-redux';
import {
FETCH_ERROR,
FETCH_START,
FETCH_SUCCESS,
} from '../../../../shared/constants/ActionTypes';
import jwtAxios, {setAuthToken} from './jwt-api';
const JWTAuthContext = createContext();
const JWTAuthActionsContext = createContext();
export const useJWTAuth = () => useContext(JWTAuthContext);
export const useJWTAuthActions = () => useContext(JWTAuthActionsContext);
const JWTAuthAuthProvider = ({children}) => {
const [firebaseData, setJWTAuthData] = useState({
user: null,
isAuthenticated: false,
isLoading: true,
});
const dispatch = useDispatch();
useEffect(() => {
const getAuthUser = () => {
const token = localStorage.getItem('token');
if (!token) {
setJWTAuthData({
user: undefined,
isLoading: false,
isAuthenticated: false,
});
return;
}
setAuthToken(token);
jwtAxios
.get('/auth')
.then(({data}) =>
setJWTAuthData({
user: data,
isLoading: false,
isAuthenticated: true,
}),
)
.catch(() =>
setJWTAuthData({
user: undefined,
isLoading: false,
isAuthenticated: false,
}),
);
};
getAuthUser();
}, []);
const signInUser = async ({email, password}) => {
dispatch({type: FETCH_START});
try {
const {data} = await jwtAxios.post('auth', {email, password});
localStorage.setItem('token', data.token);
setAuthToken(data.token);
const res = await jwtAxios.get('/auth');
setJWTAuthData({user: res.data, isAuthenticated: true, isLoading: false});
dispatch({type: FETCH_SUCCESS});
} catch (error) {
setJWTAuthData({
...firebaseData,
isAuthenticated: false,
isLoading: false,
});
dispatch({type: FETCH_ERROR, payload: error.message});
}
};
const signUpUser = async ({name, email, password}) => {
dispatch({type: FETCH_START});
try {
const {data} = await jwtAxios.post('users', {name, email, password});
localStorage.setItem('token', data.token);
setAuthToken(data.token);
const res = await jwtAxios.get('/auth');
setJWTAuthData({user: res.data, isAuthenticated: true, isLoading: false});
dispatch({type: FETCH_SUCCESS});
} catch (error) {
setJWTAuthData({
...firebaseData,
isAuthenticated: false,
isLoading: false,
});
dispatch({type: FETCH_ERROR, payload: error.message});
}
};
const logout = async () => {
localStorage.removeItem('token');
setAuthToken();
setJWTAuthData({
user: null,
isLoading: false,
isAuthenticated: false,
});
};
return (
<JWTAuthContext.Provider
value={{
...firebaseData,
}}>
<JWTAuthActionsContext.Provider
value={{
signUpUser,
signInUser,
logout,
}}>
{children}
</JWTAuthActionsContext.Provider>
</JWTAuthContext.Provider>
);
};
export default JWTAuthAuthProvider;
JWTAuthAuthProvider.propTypes = {
children: PropTypes.node.isRequired,
};
Currently it's throwing error TypeError: Cannot destructure property 'user' of 'Object(...)(...)' as it is undefined. on the line
const {user, isAuthenticated, isLoading} = useJWTAuth();
You need at least to initialize the JWTAuthContext context with an empty object.
const JWTAuthContext = createContext({});

Can not navigate to another pages after login

i dont know where is the trouble. but when i tried to login, it dont wanna navigate into the next pages and stuck in modal login. please tell me whats wrong with my code?
let navigate = useNavigate();
const dispatch = useContext(UserContext);
const state = useContext(UserContext);
// console.log(state);
const [form, setForm] = useState({
email: "",
password: "",
});
const { email, password } = form;
const handleChange = (e) => {
setForm({
...form,
[e.target.name]: e.target.value,
});
};
const handleSubmitLog = async (e) => {
try {
e.preventDefault();
const config = {
headers: {
"Content-type": "application/json",
},
};
const body = JSON.stringify(form);
const response = await API.post("/login", body, config);
console.log(response.data);
if (response?.status == 200) {
dispatch({
type: "LOGIN_SUCCESS",
payload: response.data.data,
});
if (response.data.data.status == "admin") {
navigate('/admin')
} else {
navigate('/userid')
}
}
} catch (error) {
console.log(error);
}
}
here is the response in console, i dont know why this dispatch can not work well
{
"status": "Success",
"data": {
"users": {
"name": "mr.x",
"email": "mr.x#mail.com",
"token": "asfsa"
}
}
}
TypeError: dispatch is not a function
at handleSubmitLog (header.js:59:1)
and here is the body from userContext file. please check it and tell me if my code is wrong
export const UserContext = createContext();
const initialState = {
isLogin: false,
user: {},
};
const reducer = (state, action) => {
const { type, payload } = action;
switch (type) {
case "USER_SUCCESS":
case "LOGIN_SUCCESS":
localStorage.setItem("token", payload.token)
return {
isLogin: true,
user: payload,
};
case "AUTH_ERROR":
case "LOGOUT":
localStorage.removeItem("token")
return {
isLogin: false,
user: {},
};
default:
throw new Error();
}
};
export const UserContextProvider = ({ children }) => {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<UserContext.Provider value={[state, dispatch]}>
{children}
</UserContext.Provider>
);
};
The UserContext value is an array [state, dispatch]:
<UserContext.Provider value={[state, dispatch]}>
{children}
</UserContext.Provider>
But the component is not correctly accessing the context value:
const dispatch = useContext(UserContext);
const state = useContext(UserContext);
Here both dispatch and state have are the same context value of [state, dispatch].
You need only one useContext(UserContext) access, either of the following:
Save the entire context value and use array indexing:
const userContext = useContext(UserContext);
...
// userContext[0]; state object/value
// userContext[1]; dispatch function
...
userContext[1]({
type: "LOGIN_SUCCESS",
payload: response.data.data,
});
Save the state and dispatch values directly using array destructuring assignment:
const [state, dispatch] = useContext(UserContext);
...
dispatch({
type: "LOGIN_SUCCESS",
payload: response.data.data,
});
Try this:
const { state, dispatch } = useContext(AppContext)

User Auth with React Context API

I'm using React, Axios and Mongoose. Trying to store a user state but am having trouble with the stored state.user object.
When I manually enter values for state.user, the app works properly, however when I actually login from the site, the user object is stored in localStorage but is not being read properly by the app. I noticed I had to remove new ObjectId from the object and also convert the createdAt and lastUpdated dates into strings in order for my static values to work. How can I get around this? Thanks!
Screenshot of localStorage object
context.js
const INITIAL_STATE = {
user: JSON.parse(localStorage.getItem("user")) || null,
isFetching: false,
error: false,
};
export const AuthContext = createContext(INITIAL_STATE);
export const AuthContextProvider = ({ children }) => {
const [state, dispatch] = useReducer(AuthReducer, INITIAL_STATE);
useEffect(() => {
JSON.stringify(localStorage.setItem("user", state.user));
}, [state.user]);
return (
<AuthContext.Provider
value={{
user: state.user,
isFetching: state.isFetching,
error: state.error,
dispatch,
}}
>
{children}
</AuthContext.Provider>
);
};
reducer.js
const AuthReducer = (state, action) => {
switch (action.type) {
case "LOGIN_START":
return {
user: null,
isFetching: true,
error: false,
};
case "LOGIN_SUCCESS":
return {
user: action.payload,
isFetching: false,
error: false,
};
case "LOGIN_FAILURE":
return {
user: null,
isFetching: false,
error: true,
};
case "FOLLOW":
return {
...state,
user: {
...state.user,
following: [...state.user.following, action.payload],
},
};
case "UNFOLLOW":
return {
...state,
user: {
...state.user,
following: state.user.following.filter(
(following) => following !== action.payload
),
},
};
default:
return state;
}
};
export default AuthReducer;
actions.js
export const LoginStart = (userCredentials) => ({
type: "LOGIN_START",
});
export const LoginSuccess = (user) => ({
type: "LOGIN_SUCCESS",
payload: user,
});
export const LoginFailure = (error) => ({
type: "LOGIN_FAILURE",
payload: error,
});
export const Follow = (userId) => ({
type: "FOLLOW",
payload: userId,
});
export const Unfollow = (userId) => ({
type: "UNFOLLOW",
payload: userId,
});
utils/api.js
import axios from "axios";
export const loginCall = async (userCredentials, dispatch) => {
dispatch({ type: "LOGIN_START" });
try {
const response = await axios.post("/api/auth/login", userCredentials);
dispatch({ type: "LOGIN_SUCCESS", payload: response.data });
} catch (error) {
dispatch({ type: "LOGIN_FAILURE", payload: error });
}
};

getCurentPosition() in Reactjs not updating state

I'm trying the get the user current location in my app, but even if I can see it when I console.log it it doesn't work.
I'm using an async function in order to retrieve it but I must be doing something wrong and I cannot figure out what the issue is.
ContextState
import React, { useReducer } from "react";
import RestContext from "./restContext";
import RestReducer from "./restReducer";
import Yelp from "../../Util/Yelp";
import { getCurrentPosition } from "../../Util/GeoLocation";
import {
GET_RESTAURANTS,
GET_INFO_RESTAURANT,
CLEAR_SEARCH,
SET_LOADING,
GET_LOCATION,
} from "../../types";
const RestState = (props) => {
const initalState = {
restaurants: [],
restaurant: {},
loading: false,
location: {},
};
const [state, dispatch] = useReducer(RestReducer, initalState);
// Get Restaurants
const getRestaurants = async (text) => {
setLoading();
let restaurants = await Yelp.searchRestaurants(text);
if (restaurants) {
dispatch({ type: GET_RESTAURANTS, payload: restaurants });
} else {
dispatch({ type: GET_RESTAURANTS, payload: [] });
}
};
// Get info Restaurants
const getRestaurantInfo = async (id) => {
setLoading();
let restaurant = await Yelp.searchRestaurantsInfo(id);
if (restaurant) {
dispatch({ type: GET_INFO_RESTAURANT, payload: restaurant });
} else {
dispatch({ type: GET_INFO_RESTAURANT, payload: {} });
}
};
// Clear search
const clearSearch = () => dispatch({ type: CLEAR_SEARCH });
// Set loading
const setLoading = () => dispatch({ type: SET_LOADING });
// Get location
const fetchCoordinates = async () => {
try {
const coords = await getCurrentPosition();
dispatch({ type: GET_LOCATION, payload: coords });
} catch (error) {
// Handle error
console.error(error);
}
}
return (
<RestContext.Provider
value={{
restaurants: state.restaurants,
restaurant: state.restaurant,
loading: state.loading,
getRestaurants,
clearSearch,
getRestaurantInfo,
fetchCoordinates,
}}
>
{props.children}
</RestContext.Provider>
);
};
export default RestState;
It's reducer
import {
GET_RESTAURANTS,
GET_INFO_RESTAURANT,
CLEAR_SEARCH,
SET_LOADING,
GET_LOCATION,
} from "../../types";
export default (state, action) => {
switch (action.type) {
case GET_RESTAURANTS:
return { ...state, restaurants: action.payload, loading: false };
case GET_INFO_RESTAURANT:
return { ...state, restaurant: action.payload, loading: false };
case CLEAR_SEARCH:
return { ...state, restaurants: [], loading: false };
case SET_LOADING:
return {
...state,
loading: true,
};
case GET_LOCATION:
return { ...state, location: action.payload };
default:
return state;
}
};
And the Home page when it's should be used
import React, { Fragment, useEffect, useContext } from "react";
import Search from "../../Components/restaurants/Search";
import Alert from "../../Components/layout/Alert";
import Navbar from "../../Components/layout/Navbar";
import DisplayRestaurants from "../../Components/layout/DisplayRestaurants";
import Footer from "../../Components/layout/Footer";
import { Waypoint } from "react-waypoint";
import RestContext from "../context/restaurant/restContext";
const Home = () => {
const restContext = useContext(RestContext);
useEffect(() => {
restContext.fetchCoordinates();
// eslint-disable-next-line
}, []);
const handleWaypointEnter = () => {
document.querySelector("nav").classList.remove("fixed");
};
const handleWaypointLeave = () => {
document.querySelector("nav").classList.add("fixed");
};
return (
<section className="main-home">
<Fragment>
<Navbar />
<Search />
<Alert />
<Waypoint onEnter={handleWaypointEnter} onLeave={handleWaypointLeave} />
<DisplayRestaurants />
<Footer />
</Fragment>
</section>
);
};
export default Home;
getCurrentPosition
export function getCurrentPosition(options = {}) {
return new Promise((resolve, reject) => {
navigator.geolocation.getCurrentPosition(resolve, reject, options);
});
}
coord obj
GeolocationCoordinates {latitude: 52.3555177, longitude: -1.1743196999999999, altitude: null, accuracy: 372529, altitudeAccuracy: null, …}
accuracy: 372529
altitude: null
altitudeAccuracy: null
heading: null
latitude: 52.3555177
longitude: -1.1743196999999999
speed: null
__proto__: GeolocationCoordinates
Thanks for your help
can you try this instead?
it returns a promise so in theory should be able to use .then
getCurrentPosition().then((res) => {
console.log(res) // check what `res` is
dispatch({ type: GET_LOCATION, payload: res.cords });
})

Categories

Resources