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 };
};
Related
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>
);
};
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({});
So I'm setting up a react useContext to share user's data such as the user's name and the login status etc... I make a request to the server every time a user refreshes the app to verify that if they have a valid token, the user can then be logged in. Great, but one problem I keep on running into with react is knowing how to get an instant update of useState, as this is needing to be updated as soon as the user is verified.
I've read that you have to create a new object so that React knows it's a new object and then it'll trigger a re-render... Just about every combination I can think of I've tried. Can someone please clarify for me how it's done?
Option 1:
setUser(prev => {
return { ...prev, ...newObj }
})
Option 2:
setUser({ ...newObj })
Can someone please give me the proper way to get a instant update to the React useState.
Thanks
import React, { useState } from "react";
import { useReadCookie } from "../hooks/cookies";
import UserContext from "./user-context";
const UserContextProvider = ({ children }) => {
const readCookie = useReadCookie;
const [user, setUser] = useState({
name: null,
userID: null,
token: null,
permissions: null,
loginStatus: false,
checkLoginStatusReady: false,
});
const loginStatus = async () => {
return new Promise(async (resolve, rej) => {
// So we'll reach out to the server and check to see if the
// User is logged in
const token = readCookie("token");
if (token && token.length === 174) {
// send a request to see if this is a valid user
const req = await fetch("/api/checkLoginStatus.php");
const res = await req.json();
// console.log(res);
handleLogUserIn(res);
}
console.log(user, "The state variable");
resolve();
});
};
const handleLogUserIn = (res) => {
if (res.success === true) {
const userObj = {
name: res.name,
userID: res.userID,
token: res.token,
permissions: res.permissions,
loginStatus: true,
checkLoginStatusReady: true,
};
console.log(userObj, "the user variable");
setUser({ ...userObj });
}
else {
console.log("Not going here");
const userObj = {
name: null,
userID: null,
token: null,
permissions: null,
loginStatus: false,
checkLoginStatusReady: true,
};
setUser({ ...userObj });
}
};
return (
<UserContext.Provider
value={{
username: user.username,
userID: user.userID,
token: user.token,
permissions: user.permissions,
loginStatus: user.loginStatus,
checkLoginStatusReady: loginStatus,
setUser,
}}
>
{children}
</UserContext.Provider>
);
};
export default UserContextProvider;
you can use useEffect for listening to the state update
example if using
const [state, setState] = useState({
username : "",
status : "",
})
useEffect(() => {
setState({
...state,
username : state.username,
status : "active,
})
},[state])
or if using context :
On parent provider.js:
import React, { useState } from 'react'
export const UserContext = React.createContext({
username: "",
status: "active",
setUser: () => {}
})
export const UserContextProvider = (props) => {
const setUser = (param) => {
setState({...state, username: param.username, status: param.status})
}
const initState = {
username: "",
status: "active",
setUser: () => {}
}
const [state, setState] = useState(initState)
return (
<UserContext.Provider value={state}>
{props.children}
</UserContext.Provider>
)
}
On Component:
import React, { useContext } from 'react'
import provider from './provider'
function Component() {
const context = useContext(provider.UserContext)
const onClick = () => {
// do hit api
if(token){
state.setUser({username : "a", status:"inactive"})
}
}
return (
<provider.UserContextProvider>
<buttin onClick={onClick}></button>
<div>username : {state.username}</div>
<div>status : {state.status}</div>
</provider.UserContextProvider>
)
}
userSlice.js
import { createSlice, createAsyncThunk } from "#reduxjs/toolkit";
import { API } from "../axios/index";
export const signUp = createAsyncThunk("users/signup", async (params) => {
try {
const { formData, dispatch, history } = params;
const { data } = await API.post("/users/signup", formData);
history.push("/");
dispatch(handleExistEmail(false));
return data;
} catch (error) {
console.log("aha hata var");
const { dispatch } = params;
const { status } = error.response;
if (status) {
dispatch(handleExistEmail(true));
}
}
});
export const logOut = createAsyncThunk("users/logout", async (params) => {
try {
const { id } = params;
const { data } = await API.put(`users/logout/${id}`);
localStorage.removeItem('user')
return data;
} catch (error) {
console.log(error);
}
});
const initialState = {
usersInfo: {},
status: "idle",
error: null,
existEmail: false,
};
const usersSlice = createSlice({
name: "users",
initialState,
reducers: {
handleExistEmail: (state, action) => {
state.existEmail = action.payload;
},
},
extraReducers: {
[signUp.pending]: (state, action) => {
state.status = "loading";
},
[signUp.fulfilled]: (state, action) => {
state.status = "succeeded";
state.usersInfo = action.payload;
localStorage.setItem("user", JSON.stringify(action.payload));
},
[signUp.error]: (state, action) => {
state.status = "failed";
state.error = "error";
},
},
});
export default usersSlice.reducer;
export const { handleExistEmail } = usersSlice.actions;
Auth.jsx
import React, { useState } from "react";
import { Container, Row, Col, Form, Button } from "react-bootstrap";
import { signUp } from "../features/usersSlice";
import { useDispatch, useSelector } from "react-redux";
import Message from "../components/Message";
const AuthScreen = ({ history }) => {
const existEmail = useSelector((state) => state.users.existEmail);
const dispatch = useDispatch();
const [login, setLogin] = useState(true);
const [formData, setFormData] = useState({
email: "",
password: "",
confirmPassword: "",
firstName: "",
lastName: "",
});
const handleSignUp = (e) => {
console.log("kayıt olma işlemi çalıştı")
if (formData.password === formData.confirmPassword) {
e.preventDefault();
dispatch(signUp({ formData, dispatch, history }));
} else {
e.preventDefault();
}
};
return (
<>
<Container>...
</>
);
};
export default AuthScreen;
userRouter.js
import express from "express";
import User from "../models/userModel.js";
import bcrypt from "bcryptjs";
import jwt from "jsonwebtoken";
import tokenModel from "../models/tokenModel.js";
const router = express.Router();
router.post("/signup", async (req, res) => {
try {
const { email, password, confirmPassword, firstName, lastName } = req.body;
const userExists = await User.findOne({ email });
if (userExists) {
console.log("A user with this email already exists");
return res
.status(400)
.json({ message: "A user with this email already exists" });
}
if (password !== confirmPassword) {
return res.status(400).json({ message: "Passwords don't match" });
}
const hashedPassword = await bcrypt.hash(password, 10);
const user = await User.create({
email,
name: `${firstName} ${lastName}`,
password: hashedPassword,
});
const accessToken = jwt.sign(
{ email: user.email, id: user._id },
process.env.ACCESS_TOKEN_SECRET,
{
expiresIn: "3m",
}
);
const refreshToken = jwt.sign(
{ email: user.email, id: user._id },
process.env.REFRESH_TOKEN
);
await tokenModel.create({
userId: user._id,
refreshToken: refreshToken,
});
res.status(200).json({ user, accessToken });
} catch (error) {
console.log(error);
}
});
Hi all.I try to fill my redux state when i sign up with users infos and it works well when i sign up. But when i refresh page my redux state gets erased. I used redux-persist but it didn't work either. I have searched many websites for solution to fix it but everyone suggest redux-persist but do i have to use a ready-made package ? what is the source of this issue ?
I'm using react context & axios interceptors to hide/show a loading spinner. While it does hide and show correctly when requests and responses are fired, my loading spinner is only showing at the start of a network request. The request fires and then goes into a 'pending' status. During the 'pending' status, the response interceptor is fired and hides the loading spinner.
How can I make sure the loading spinner stays visible during the pending requests?
I tried adding some console logs to fire when the requests, responses, and errors were returned including the count, and it showed it (for two requests) to successfully go from 0 - 1 - 2 - 1 - 0, but in the chrome devtools network tab showed as pending even though all requests were returned.
EDIT: thought I had it working after some refactor but it was a no-go. Added updated code
import React, { useReducer, useRef, useEffect, useCallback } from "react";
import { api } from "api/api";
import LoadingReducer from "reducer/LoadingReducer";
const LoadingContext = React.createContext();
export const LoadingProvider = ({ children }) => {
const [loader, dispatch] = useReducer(LoadingReducer, {
loading: false,
count: 0,
});
const loaderKeepAlive = useRef(null),
showLoader = useRef(null);
const showLoading = useCallback(() => {
dispatch({
type: "SHOW_LOADING",
});
}, [dispatch]);
const hideLoading = useCallback(() => {
loaderKeepAlive.current = setTimeout(() => {
dispatch({
type: "HIDE_LOADING",
});
}, 3000);
return clearTimeout(loaderKeepAlive.current);
}, [dispatch]);
const requestHandler = useCallback(
(request) => {
dispatch({ type: "SET_COUNT", count: 1 });
return Promise.resolve({ ...request });
},
[dispatch]
);
const errorHandler = useCallback(
(error) => {
dispatch({ type: "SET_COUNT", count: -1 });
return Promise.reject({ ...error });
},
[dispatch]
);
const successHandler = useCallback(
(response) => {
dispatch({ type: "SET_COUNT", count: -1 });
return Promise.resolve({ ...response });
},
[dispatch]
);
useEffect(() => {
if (loader.count === 0) {
hideLoading();
clearTimeout(showLoader.current);
} else {
showLoader.current = setTimeout(() => {
showLoading();
}, 1000);
}
}, [showLoader, showLoading, hideLoading, loader.count]);
useEffect(() => {
if (!api.interceptors.request.handlers[0]) {
api.interceptors.request.use(
(request) => requestHandler(request),
(error) => errorHandler(error)
);
}
if (!api.interceptors.response.handlers[0]) {
api.interceptors.response.use(
(response) => successHandler(response),
(error) => errorHandler(error)
);
}
return () => {
clearTimeout(showLoader.current);
};
}, [errorHandler, requestHandler, successHandler, showLoader]);
return (
<LoadingContext.Provider
value={{
loader,
}}
>
{children}
</LoadingContext.Provider>
);
};
export default LoadingContext;
I think a more standard approach would be to just utilize the loading state to conditionally render the Spinner and the result of the promise to remove it from the DOM (see demo below). Typically, the interceptors are used for returning the error from an API response, since axios defaults to the status error (for example, 404 - not found).
For example, a custom axios interceptor to display an API error:
import get from "lodash.get";
import axios from "axios";
const { baseURL } = process.env;
export const app = axios.create({
baseURL
});
app.interceptors.response.use(
response => response,
error => {
const err = get(error, ["response", "data", "err"]);
return Promise.reject(err || error.message);
}
);
export default app;
Demo
Code
App.js
import React, { useEffect, useCallback, useState } from "react";
import fakeApi from "./api";
import { useAppContext } from "./AppContext";
import Spinner from "./Spinner";
const App = () => {
const { isLoading, error, dispatch } = useAppContext();
const [data, setData] = useState({});
const fetchData = useCallback(async () => {
try {
// this example uses a fake api
// if you want to trigger an error, then pass a status code other than 200
const res = await fakeApi.get(200);
setData(res.data);
dispatch({ type: "loaded" });
} catch (error) {
dispatch({ type: "error", payload: error.toString() });
}
}, [dispatch]);
const reloadData = useCallback(() => {
dispatch({ type: "reset" });
fetchData();
}, [dispatch, fetchData]);
useEffect(() => {
fetchData();
// optionally reset context state on unmount
return () => {
dispatch({ type: "reset" });
};
}, [dispatch, fetchData]);
if (isLoading) return <Spinner />;
if (error) return <p style={{ color: "red" }}>{error}</p>;
return (
<div style={{ textAlign: "center" }}>
<pre
style={{
background: "#ebebeb",
margin: "0 auto 20px",
textAlign: "left",
width: 600
}}
>
<code>{JSON.stringify(data, null, 4)}</code>
</pre>
<button type="button" onClick={reloadData}>
Reload
</button>
</div>
);
};
export default App;
AppContext.js
import React, { createContext, useContext, useReducer } from "react";
const AppContext = createContext();
const initialReducerState = {
isLoading: true,
error: ""
};
const handleLoading = (state, { type, payload }) => {
switch (type) {
case "loaded":
return { isLoading: false, error: "" };
case "error":
return { isLoading: false, error: payload };
case "reset":
return initialReducerState;
default:
return state;
}
};
export const AppContextProvider = ({ children }) => {
const [state, dispatch] = useReducer(handleLoading, initialReducerState);
return (
<AppContext.Provider
value={{
...state,
dispatch
}}
>
{children}
</AppContext.Provider>
);
};
export const useAppContext = () => useContext(AppContext);
export default AppContextProvider;
Spinner.js
import React from "react";
const Spinner = () => <div className="loader">Loading...</div>;
export default Spinner;
fakeApi.js
const data = [{ id: "1", name: "Bob" }];
export const fakeApi = {
get: (status) =>
new Promise((resolve, reject) => {
setTimeout(() => {
status === 200
? resolve({ data })
: reject(new Error("Unable to locate data."));
}, 2000);
})
};
export default fakeApi;
index.js
import React from "react";
import ReactDOM from "react-dom";
import AppContextProvider from "./AppContext";
import App from "./App";
import "./styles.css";
const rootElement = document.getElementById("root");
ReactDOM.render(
<React.StrictMode>
<AppContextProvider>
<App />
</AppContextProvider>
</React.StrictMode>,
rootElement
);