Reputation: 15
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>
);
Upvotes: 1
Views: 78
Reputation: 202751
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 };
};
Upvotes: 1