I have this scenario that is after the user login and assuming it is success, user details / user token is stored to localStorage and will automatically navigate to dashboard page, dashboard page has some api calls and those api calls required/needs token that is stored in the localStorage, my problem is that it is unable to retrieve those values in localStorage, but when I check from localStorage using console, the key/value is there, I noticed that, I need to refresh the page to retrieve those details without a problem. How can I possibly fix this issue? to be able to get localStorage value after navigating to another component?
Here is my code for index.tsx
ReactDOM.render(
<AuthContextProvider>
<App />
</AuthContextProvider>,
document.getElementById("root")
);
AuthContext code:
const AuthContext = React.createContext({
user: "",
isLoggedIn: false,
login: (userdata: any, expirationTime: string) => {},
logout: () => {},
});
export const AuthContextProvider = (props: any) => {
const initialUser = localStorage.getItem("user") || "";
const [userData, setUserData] = useState(initialUser);
const userIsLoggedIn = !!userData;
const logoutHandler = () => {
setUserData("");
localStorage.removeItem("user");
};
const loginHandler = async (
user: any,
expirationTime: string
) => {
localStorage.setItem("user", JSON.stringify(user));
setUserData(user);
};
const contextValue = {
user: userData,
isLoggedIn: userIsLoggedIn,
login: loginHandler,
logout: logoutHandler,
};
return (
<AuthContext.Provider value={contextValue}>
{props.children}
</AuthContext.Provider>
);
};
export default AuthContext;
App.tsx code
function App() {
const authCtx = useContext(AuthContext);
return (
<BrowserRouter>
<Routes>
{!authCtx.isLoggedIn && (
<Route element={<LoginLayout />}>
<Route index element={<SignInForm />} />
{/*Other links here */}
</Route>
)}
{authCtx.isLoggedIn && (
<Route element={<AdminLayout />}>
<Route path="dashboard" element={<DashboardScreen />} />
{/*Other links here */}
</Route>
)}
<Route path="*" element={<PageNotFound />} />
</Routes>
</BrowserRouter>
);
}
Login code:
try {
await AuthService.login(email, password).then(
(res) => {
authCtx.login(res, "0");
navigate("../dashboard", { replace: true });
},
(error) => {
}
);
} catch (err) {
console.log(err);
}
Dashboard code:
const loadCountOnlineUsers = useCallback(async () => {
try {
await DashboardService.loadCountOnlineUsers().then(
(res) => {
setCntOnlineUsers(res.count);
setflagOnlineUsers(false);
},
(error) => {
setflagOnlineUsers(false);
}
);
} catch (err) {
console.log(err);
setflagOnlineUsers(false);
}
}, [setCntOnlineUsers, setflagOnlineUsers]);
useEffect(() => {
loadCountOnlineUsers();
}, [loadCountOnlineUsers]);
Dashboard service code:
const config = {
headers: {
"Content-Type": "application/json",
Authorization: AuthHeader(),
},
params: {},
};
const loadCountOnlineUsers = () => {
config["params"] = {};
return axios
.get(API_URL + "api/v1/dashboard-related/online-users", config)
.then((response) => {
return response.data;
});
};
const DashboardService = {
loadCountOnlineUsers,
};
export default DashboardService;
Auth-header code:
export default function AuthHeader() {
const user = JSON.parse(localStorage.getItem("user") || "{}");
if (user && user.token) {
return "Bearer " + user.token;
} else {
return "";
}
}
The problem is that the check to localStorage in AuthHeader() isn't updating reactively. The fix would be to rewrite AuthHeader to accept the user data like this:
export default function AuthHeader(user) {
const user = JSON.parse(user || "{}");
if (user && user.token) {
return "Bearer " + user.token;
} else {
return "";
}
}
and then continue the data piping into the area where AuthHeader() is called, perhaps like this:
const config = (user) => ({
headers: {
"Content-Type": "application/json",
Authorization: AuthHeader(),
},
params: {},
});
const loadCountOnlineUsers = (user) => {
config["params"] = {};
return axios
.get(API_URL + "api/v1/dashboard-related/online-users", config(user))
.then((response) => {
return response.data;
});
};
const DashboardService = {
loadCountOnlineUsers,
};
Lastly, using an effect in the dashboard to update it reactively, while connecting to context:
const authCtx = useContext(AuthContext);
const user = authCtx.user;
const loadCountOnlineUsers = (user) => {
return useCallback(async () => {
try {
await DashboardService.loadCountOnlineUsers(user).then(
(res) => {
setCntOnlineUsers(res.count);
setflagOnlineUsers(false);
},
(error) => {
setflagOnlineUsers(false);
}
);
} catch (err) {
console.log(err);
setflagOnlineUsers(false);
}
}, [setCntOnlineUsers, setflagOnlineUsers]);
}
useEffect(() => {
loadCountOnlineUsers(user);
}, [loadCountOnlineUsers, user]);
Related
I am trying this code with useContext. i want to take this response.data.message in Editdata.js component. how did i do? how i will send this state message using context.
auth-context.js
const AuthContext = React.createContext({
updatePlayer: () => {},
});
export const AuthContextProvider = (props) => {
const [msg, setmsg] = useState();
const playerupdate = async (updatedPlayer) => {
const { id, player_fname, player_lname, player_nickname } = updatedPlayer;
await axios
.post(
"https://scorepad.ominfowave.com/api/adminPlayerUpdate",
JSON.stringify({ id, player_fname, player_lname, player_nickname }),
{
headers: { "Content-Type": "application/json" },
}
)
.then((response) => {
fetchPlayerList()
//navigate('/view-players');
setmsg(response.data.message)
})
.catch((error) => {
alert(error);
});
};
return (
<AuthContext.Provider
value={{
updatePlayer: playerupdate
}}
>
{props.children}
</AuthContext.Provider>
);
};
type here
Editdata.js
function Editdata() {
const authcon = useContext(AuthContext);
const submitupdateForm = (e) => {
e.preventDefault();
const playerdetail = {
player_fname: firstname,
player_lname: lastname,
player_nickname: nickname,
id: empid
}
authcon.updatePlayer(playerdetail)
}
return (
<form onSubmit={submitupdateForm}>
</form>
);
}
How is the correct way to pass state between components with useContext?
I keep getting this error with my App.js file, below is the code for App.js :-
import React, { useRef, useState, useEffect, useCallback } from "react";
import { BrowserRouter as Router, Route, Switch, Redirect } from "react-router-dom";
import Web3 from "web3";
import { StreamChat } from "stream-chat";
import axios from "axios";
// Importing abi
import Musicosmos from "../abis/Musicosmos.json";
// Importing css
import "./App.css";
// Importing components
import Navbar from "./Layout/Navbar/Navbar";
import Footer from "./Layout/Footer/Footer";
import ScrollToTop from "./Utils/ScrollToTop/ScrollToTop";
import Loading from "./Utils/Loading/Loading";
import HomePage from "./Homepage/HomePage";
import Library from "./Library/Library";
import Faq from "./Faq/Faq";
import Dashboard from "./Dashboard/Dashboard";
import ContactUs from "./ContactUs/ContactUs";
import SongInfo from "./SongInfo/SongInfo";
import Trending from "./Trending/Trending";
import ReportABug from "./ReportABug/ReportABug";
import Create from "./Create/Create";
import PageNotFound from "./PageNotFound/PageNotFound";
import StreamChatComponent from "./StreamChat/StreamChatComponent";
// Importing ENV variables
import contractAddress from "../config/address";
import configData from "../config/config.json";
const ENV = JSON.parse(JSON.stringify(configData));
// Initializing IPFS and StreamChat clients
const ipfsClient = require("ipfs-http-client");
var streamClient = StreamChat.getInstance(ENV.STREAM_API_KEY);
function App() {
var enc = new TextEncoder();
// Initializing states
const [streamAuthToken, setStreamAuthToken] = useState("");
const [loading, setLoading] = useState(true);
const [account, setAccount] = useState("");
const [musicosmos, setMusicosmos] = useState("");
const [songNFTs, setSongNFTs] = useState([]);
const [imageBuffer, setImageBuffer] = useState(Buffer(enc.encode("-")));
const [songBuffer, setSongBuffer] = useState(Buffer(enc.encode("-")));
const [songLyrics, setSongLyrics] = useState("");
const [lyricsBuffer, setLyricsBuffer] = useState(Buffer(enc.encode("-")));
const [songDescription, setSongDescription] = useState("");
const [descriptionBuffer, setDescriptionBuffer] = useState(Buffer(enc.encode("-")));
const [evmosUSD, setEvmosUSD] = useState("");
const [evmosINR, setEvmosINR] = useState("");
// Modal States
const [showTradeSuccess, setShowTradeSuccess] = useState(false);
const [showCreateSuccess, setShowCreateSuccess] = useState(false);
const [showError, setShowError] = useState(false);
var web3 = useRef();
const ipfs = ipfsClient({
host: "ipfs.infura.io",
port: 5001,
protocol: "https",
});
const { ethereum } = window;
if (ethereum) {
ethereum.on("accountsChanged", async function (accounts) {
if (web3.current) {
setAccount(web3.current.utils.toChecksumAddress(accounts[0]));
}
window.location.reload();
});
}
async function fetchEvmosUSD() {
const COINBASE_BASE_URL = "https://api.coinbase.com/v2";
const res = await fetch(`${COINBASE_BASE_URL}/prices/MATIC-USD/buy`);
const data = await res.json();
setEvmosUSD(data.data.amount);
}
async function fetchEvmosINR() {
const COINBASE_BASE_URL = "https://api.coinbase.com/v2";
const res = await fetch(`${COINBASE_BASE_URL}/prices/MATIC-INR/buy`);
const data = await res.json();
setEvmosINR(data.data.amount);
}
const loadBlockchainData = useCallback(async () => {
var accounts;
async function addEvmosTestnetNetwork() {
try {
await ethereum.request({
method: "wallet_switchEthereumChain",
params: [{ chainId: "0x2328" }], // Hexadecimal version of 9000, prefixed with 0x
});
} catch (error) {
if (error.code === 4902) {
try {
await ethereum.request({
method: "wallet_addEthereumChain",
params: [
{
chainId: "0x2328", // Hexadecimal version of 9000, prefixed with 0x
chainName: "Evmos Testnet",
nativeCurrency: {
name: "EVMOS",
symbol: "tEVMOS",
decimals: 18,
},
rpcUrls: ["https://eth.bd.evmos.dev:8545"],
blockExplorerUrls: ["https://evm.evmos.dev"],
iconUrls: [""],
},
],
});
} catch (addError) {
console.log("Did not add network");
}
}
}
}
if (ethereum) {
window.web3 = new Web3(ethereum);
await addEvmosTestnetNetwork();
} else if (ethereum && (await ethereum.request({ method: "net_version" })) !== "9000") {
window.web3 = new Web3(window.web3.currentProvider);
await addEvmosTestnetNetwork();
} else {
// const web3 = new Web3(Web3.givenProvider || "http://localhost:7545");
window.web3 = new Web3(new Web3.providers.HttpProvider("https://polygon-mumbai.infura.io/v3/6f89b4b5242a4191af04c7939d66d6e8"));
window.alert(
"Non-Ethereum browser detected. You cannot perform any transactions on the blockchain, however you will still be able to watch all content present on the blockchain. To make transactions you should consider installing Metamask"
);
}
web3.current = window.web3;
// This results in a bug which converts all uppercase alphabets in the address to lowercase
// accounts = await window.ethereum.request({ method: "eth_accounts" });
accounts = await web3.current.eth.getAccounts();
// accounts = await ethereum.request({ method: "eth_requestAccounts" });
// Load account
setAccount(accounts[0]);
const networkData = Musicosmos.networks[ENV.BLOCKCHAIN_NETWORK_ID];
if (networkData) {
const _musicosmos = new web3.current.eth.Contract(Musicosmos.abi, networkData.address);
setMusicosmos(_musicosmos);
// Fetch all NFTs
var _nfts;
try {
_nfts = await _musicosmos.methods.fetchAllNFTs().call();
setSongNFTs(_nfts);
} catch (err) {
console.log(err);
}
setLoading(false);
} else {
window.alert("Musicosmos contract not deployed to detected network.");
}
}, [ethereum]);
const StreamAuth = async (account, setStreamAuthToken) => {
const {
data: { token },
} = await axios.post(`${ENV.SERVER_URL}/chat-signin`, {
account,
});
setStreamAuthToken(token);
};
const streamAuthCheck = useCallback(async (streamAuthToken, setStreamAuthToken, account) => {
if (streamAuthToken && account) {
streamClient.connectUser(
{
id: account.substring(2),
account: account,
},
streamAuthToken
);
}
if (!streamAuthToken && account) {
await StreamAuth(account, setStreamAuthToken);
}
}, []);
useEffect(() => {
async function tasks() {
await loadBlockchainData();
await streamAuthCheck(streamAuthToken, setStreamAuthToken, account);
}
tasks();
fetchEvmosUSD();
fetchEvmosINR();
// setLoading(false);
}, [loadBlockchainData, streamAuthCheck, account, streamAuthToken]);
async function captureLyrics(event) {
event.preventDefault();
const file = new Blob([event.target.value.length > 0 ? event.target.value : "-"], { type: "text/plain" });
if (file) {
const reader = new window.FileReader();
reader.readAsArrayBuffer(file);
reader.onloadend = () => {
setLyricsBuffer(Buffer(reader.result));
};
} else {
return;
}
}
async function captureDescription(event) {
event.preventDefault();
const file = new Blob([event.target.value.length > 0 ? event.target.value : "-"], { type: "text/plain" });
if (file) {
const reader = new window.FileReader();
reader.readAsArrayBuffer(file);
reader.onloadend = () => {
setDescriptionBuffer(Buffer(reader.result));
};
} else {
return;
}
}
async function captureImage(event) {
event.preventDefault();
const file = event.target.files[0];
if (file) {
const reader = new window.FileReader();
reader.readAsArrayBuffer(file);
reader.onloadend = () => {
setImageBuffer(Buffer(reader.result));
};
} else {
return;
}
}
async function captureSong(event) {
event.preventDefault();
if (document.getElementById("upload-song")) {
var uploadSong = document.getElementById("upload-song");
if (uploadSong.files[0].size > 100700000) {
alert("File size is too big! Please upload a file smaller than 100MB.");
return;
}
}
const file = event.target.files[0];
if (file) {
const reader = new window.FileReader();
reader.readAsArrayBuffer(file);
reader.onloadend = () => {
setSongBuffer(Buffer(reader.result));
};
} else {
return;
}
}
async function createSong(_name, _artistName, _price, _onSale, _links, _characteristics) {
var _imgHash;
var _songHash;
var _lyricsHash;
var _descriptionHash;
setLoading(true);
await ipfs.add(imageBuffer, async (error, _imgResult) => {
console.log("_imgResult:", _imgResult);
if (error) {
console.error(error);
return;
}
_imgHash = _imgResult[0].hash;
console.log("imgHash:", _imgHash);
ipfs.add(songBuffer, async (error, _songResult) => {
console.log("_songResult:", _songResult);
if (error) {
console.error(error);
return;
}
_songHash = _songResult[0].hash;
console.log("_songHash:", _songHash);
ipfs.add(descriptionBuffer, async (error, _descriptionResult) => {
console.log("_descriptionResult:", _descriptionResult);
if (error) {
console.error(error);
return;
}
_descriptionHash = descriptionBuffer.equals(Buffer(enc.encode("-"))) ? [] : _descriptionResult[0].hash;
console.log("_descriptionHash:", _descriptionHash);
ipfs.add(lyricsBuffer, async (error, _lyricsResult) => {
console.log("_lyricsResult:", _lyricsResult);
if (error) {
console.error(error);
return;
}
_lyricsHash = lyricsBuffer.equals(Buffer(enc.encode("-"))) ? [] : _lyricsResult[0].hash;
console.log("_lyricsHash:", _lyricsHash);
// Set up your Ethereum transaction
const transactionParameters = {
to: contractAddress, // Required except during contract publications.
from: account, // must match user's active address.
data: musicosmos.methods
.createSong(
_name,
_artistName,
window.web3.utils.toWei(_price, "Ether"),
_imgHash,
_songHash,
_descriptionHash,
_lyricsHash,
_onSale,
_links,
_characteristics
)
.encodeABI(), //make call to NFT smart contract
};
// Sign the transaction via Metamask
try {
const txHash = await window.ethereum.request({
method: "eth_sendTransaction",
params: [transactionParameters],
});
setLoading(false);
setShowCreateSuccess(true);
return {
success: true,
status: "Check out your transaction on Etherscan: https://ropsten.etherscan.io/tx/" + txHash,
};
} catch (error) {
setLoading(false);
setShowError(true);
return {
success: false,
status: "Something went wrong: " + error.message,
};
}
});
});
});
});
}
const createTeamChannel = async (_artistAddress, _artistName) => {
var selectedMembers = [streamClient.userID, _artistAddress.substring(2)];
try {
const newChannel = await streamClient.channel("team", _artistAddress, {
name: _artistName + "'s Room",
members: selectedMembers,
});
await newChannel.watch();
await newChannel.addMembers([streamClient.userID]);
selectedMembers = [];
} catch (error) {
console.log(error);
}
};
const createMessagingChannel = async (_artistAddress, _artistName) => {
var selectedUsers = [streamClient.userID, _artistAddress.substring(2)];
var _channelId = streamClient.userID.substring(0, 20).concat(_artistAddress.substring(2, 22));
try {
const newChannel = await streamClient.channel("messaging", _channelId, {
name: _artistName,
members: selectedUsers,
account: _artistAddress,
});
await newChannel.watch();
selectedUsers = [];
} catch (error) {
console.log(error);
}
};
async function purchaseSong(id, price, _artistAddress, _artistName) {
const _id = parseInt(id).toString();
// const _price = web3.utils.fromWei(price.toString(), 'Ether')
setLoading(true);
musicosmos.methods
.purchaseSong(_id)
.send({ from: account, value: price })
.once("receipt", async (receipt) => {
if (account !== _artistAddress) {
await createMessagingChannel(_artistAddress, _artistName);
await createTeamChannel(_artistAddress, _artistName);
}
setLoading(false);
setShowTradeSuccess(true);
})
.catch(function (error) {
setLoading(false);
setShowError(true);
// if (error.code === 4001) {
// window.location.reload();
// }
});
}
function closeTradeSuccessModal() {
setShowTradeSuccess(false);
window.location.reload();
}
function closeCreateSuccessModal() {
setShowCreateSuccess(false);
window.location.reload();
}
async function toggleOnSale(id) {
const _id = parseInt(id).toString();
setLoading(true);
musicosmos.methods
.toggleOnSale(_id)
.send({ from: account })
.once("receipt", (receipt) => {
setLoading(false);
window.location.reload();
})
.catch(function (error) {
if (error.code === 4001) {
window.location.reload();
} else {
// show error message
}
});
}
async function updatePrice(id, price) {
const _id = parseInt(id).toString();
const _price = web3.current.utils.toWei(price, "Ether");
setLoading(true);
musicosmos.methods
.updatePrice(_id, _price)
.send({ from: account })
.once("receipt", (receipt) => {
setLoading(false);
window.location.reload();
})
.catch(function (error) {
if (error.code === 4001) {
window.location.reload();
}
});
}
return (
<Router>
<ScrollToTop />
<Navbar />
<Switch>
<Route exact path="/" component={HomePage} />
<Route
exact
path="/chat"
render={() =>
loading ? (
<Loading />
) : (
<StreamChatComponent
account={account}
client={streamClient}
streamAuthToken={streamAuthToken}
setStreamAuthToken={setStreamAuthToken}
streamAuthCheck={streamAuthCheck}
/>
)
}
/>
<Route
exact
path="/song-info/:songId"
render={(props) =>
loading ? (
<Loading />
) : (
<SongInfo
{...props}
account={account}
songNFTs={songNFTs}
purchaseSong={purchaseSong}
toggleOnSale={toggleOnSale}
updatePrice={updatePrice}
evmosUSD={evmosUSD}
evmosINR={evmosINR}
showTradeSuccess={showTradeSuccess}
closeTradeSuccessModal={closeTradeSuccessModal}
/>
)
}
/>
<Route exact path="/library" render={() => (loading ? <Loading /> : <Library songNFTs={songNFTs} />)} />
<Route exact path="/trending" render={() => (loading ? <Loading /> : <Trending songNFTs={songNFTs} />)} />
<Route exact path="/dashboard" render={() => (loading ? <Loading /> : <Dashboard account={account} songNFTs={songNFTs} />)} />
<Route
exact
path="/create"
render={(props) =>
loading ? (
<Loading />
) : (
<Create
{...props}
createSong={createSong}
captureImage={captureImage}
songLyrics={songLyrics}
setSongLyrics={setSongLyrics}
captureLyrics={captureLyrics}
songDescription={songDescription}
setSongDescription={setSongDescription}
captureDescription={captureDescription}
captureSong={captureSong}
evmosUSD={evmosUSD}
evmosINR={evmosINR}
showCreateSuccess={showCreateSuccess}
closeCreateSuccessModal={closeCreateSuccessModal}
showError={showError}
/>
)
}
/>
<Route exact path="/faq" component={Faq} />
<Route exact path="/bugs" component={ReportABug} />
<Route exact path="/contact-us" component={ContactUs} />
<Route path="/404" exact component={PageNotFound} />
<Redirect to="/404" />
</Switch>
<Footer />
</Router>
);
}
export default App;
The error that I am facing is this :-
<Unhandled Rejection (TypeError): Cannot read properties of undefined (reading '9000')
_callee3$
http://localhost:3000/static/js/main.chunk.js:2117:86
2114 | // accounts = await ethereum.request({ method: "eth_requestAccounts" });
2115 | // Load account
2116 | setAccount(accounts[0]);
2117 | networkData = abis_Musicosmos_json__WEBPACK_IMPORTED_MODULE_9_.networks[ENV.BLOCKCHAIN_NETWORK_ID];
| ^ 2118 |
2119 | if (!networkData) {
2120 | _context3.next = 44;>
This is the file which contains the variable "BLOCKCHAIN_NETWORK_ID"
"SERVER_URL": "http://musicosmos-qor6o6w8q-mayanksahoo07.vercel.app/",
"STREAM_API_KEY": "85u37g9jgfyk",
"BLOCKCHAIN_NETWORK_ID": 9000,
"SPOTIFY_CLIENT_ID": "bfe84d35098b41f085842ab4e9dd1b79",
"SPOTIFY_CLIENT_SECRET": "4ef7926b8d8d406ca04c842170403eb0"
}```
Can someone please help?
Good day, I faced an issue when tried to push the user to the dashboard after the user login correctly but it didn't, here is the code below:
LoginForm.js
const { isLoading, isAuth, error, message } = useSelector(
(state) => state.login
);
const handleSubmit = (e) => {
e.preventDefault();
console.log(values);//values={email:'..', pass:'..'}
if (formValidation()) {
dispatch(NewUserLogin(values));
console.log(isAuth); //print false but in redux state print true
if (isAuth) history.push('/dashboard');
}
};
LoginAction.js
export const NewUserLogin = (formValues) => async (dispatch) => {
try {
dispatch(loginPending());
const { status, message } = await LoginAPIRequest(formValues);
if (status === 'success') {
dispatch(loginSuccess(message));
} else {
dispatch(loginFailure(message));
}
console.log(status);
console.log(message);
} catch (error) {
dispatch(loginFailure(error.message));
}
};
loginSlice.js
import { createSlice } from '#reduxjs/toolkit';
const initialState = {
isLoading: false,
isAuth: false,
error: '',
};
const loginSlice = createSlice({
name: 'Login',
initialState,
reducers: {
loginPending: (state) => {
state.isLoading = true;
},
loginSuccess: (state, { payload }) => {
state.isLoading = false;
state.isAuth = true;
state.message = payload;
state.error = '';
},
loginFailure: (state, { payload }) => {
//actions.payload or shortcut {payload}
state.isLoading = false;
state.error = payload;
},
},
});
const { reducer, actions } = loginSlice;
export const { loginPending, loginSuccess, loginFailure } = actions;
export default reducer;
userAPI.js
import { createEndpointsAPI, ENDPOINTS } from './index';
export const LoginAPIRequest = (formValues) => {
return new Promise(async (resolve, reject) => {
//call api
try {
await createEndpointsAPI(ENDPOINTS.LOGIN)
.create(formValues)
.then((res) => {
resolve(res.data);
if (res.data.status === 'success') {
resolve(res.data);
sessionStorage.setItem('accessJWT', res.data.accessJWT);
localStorage.setItem('sms', JSON.stringify(res.data.refreshJWT));
}
console.log(res.data);
})
.catch((err) => {
reject(err);
});
} catch (error) {
console.log(error);
reject(error);
}
});
};
index.js (root API)
import axios from 'axios';
export const ENDPOINTS = {
LOGIN: 'user/login',
LOGOUT: 'user/logout',
REGISTER: 'user/register',
};
const baseURL = 'http://localhost:3040/v2/';
export const createEndpointsAPI = (endpoint) => {
let url = baseURL + endpoint + '/';
return {
fetchAll: () => axios.get(url),
fetchById: (id) => axios.get(url + id),
create: (newData) => axios.post(url, newData),
update: (updateData, id) => axios.put(url + id, updateData),
delete: (id) => axios.delete(url + id),
};
};
App.js
<MuiThemeProvider theme={theme}>
<CssBaseline />
<Router>
<Switch>
<Route path='/' exact>
<Login />
</Route>
<PrivateRoute path='/dashboard'>
<Dashboard />
</PrivateRoute>
<Route path='*' component={() => '404 NOT FOUND'} />
</Switch>
</Router>
</MuiThemeProvider>
PrivateRoute.js
import { useSelector } from 'react-redux';
const PrivateRoute = ({ component: Component, ...rest }) => {
const { isAuth } = useSelector((state) => state.login);
console.log(isAuth);
return (
<Route
{...rest}
render={(props) => {
isAuth ? (
<Component {...props} />
) : (
<Redirect
to={{
pathname: '/',
state: { from: props.location },
}}
/>
);
}}
/>
);
};
export default PrivateRoute;
The problem is, isAuth is a redux state, it should return true when the user login correctly, but it's not, I console.log(isAuth) and it prints false for the first time even user login correctly, and if I click login one more time it prints true in the console log and redirects the user to the dashboard page. I don't know why isAuth is returned false for the first time when use is login correctly? Please help check the above code from top to bottom, I provide you everythings.
The log: console.log(isAuth); logs a stale closure, you could try an effect on isAuth and redirect when it's true.
Here is an example:
const Component = (propps) => {
const { isLoading, isAuth, error, message } = useSelector(
(state) => state.login
);
const handleSubmit = (e) => {
//...dispatches but doesn't check isAuth
};
useEffect(() => {
//go to dashboard if isAuth is true
if (isAuth) history.push('/dashboard');
}, [isAuth]);//run effect when isAuth changes
};
I have a bit of a problem implementing authentication for my React application. I followed this link to get the authentication going. Here's my App component:
function App() {
return (
<ProvideAuth>
<BrowserRouter>
<Header />
<Switch>
<PrivateRoute exact path="/">
<Dashboard />
</PrivateRoute>
<Route path="/login">
<Login />
</Route>
</Switch>
</BrowserRouter>
</ProvideAuth>
);
}
function PrivateRoute({ children, ...rest }) {
let auth = useAuth();
console.log("USER: ", auth.user);
return (
<Route
{...rest}
render={({ location }) =>
auth.user ? (
children
) : (
<Redirect
to={{
pathname: "/login",
state: { from: location }
}}
/>
)} />
)
}
export default App;
Login component:
const Login = () => {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
let history = useHistory();
let location = useLocation();
let auth = useAuth();
let { from } = location.state || { from: { pathname: "/" } }
let login = (e) => {
auth.signin(email, password, () => {
history.replace(from);
});
};
return (
<div>
<input onChange={e => setEmail(e.target.value)} value={email} type="email" />
<input onChange={e => setPassword(e.target.value)} value={password} type="password" />
</div>
)
}
export default Login;
Finally use-auth.js:
const authContext = createContext();
export function ProvideAuth({ children }) {
const auth = useProvideAuth();
return <authContext.Provider value={auth}>{children}</authContext.Provider>;
};
export const useAuth = () => {
return useContext(authContext);
};
function useProvideAuth() {
const [user, setUser] = useState(null);
const signin = (email, password, callback) => {
axios.post(`${apiUrl}/sign_in`, {
'email': email,
'password': password
},
{
headers: {
'Content-Type': 'application/json'
}
}).then(res => {
const expiryDate = new Date(new Date().getTime() + 6 * 60 * 60 * 1000).toUTCString();
document.cookie = `access-token=${res.headers['access-token']}; path=/; expires=${expiryDate}; secure; samesite=lax`;
return res.data
})
.then(data => {
setUser(data.data);
callback();
})
.catch(e => {
setUser(null);
});
};
const signout = () => {
document.cookie = "access-token=; expires = Thu, 01 Jan 1970 00:00:00 GMT";
setUser(null);
}
useEffect(() => {
const cookies = getCookies();
if (cookies['access-token']) {
axios.get(`${apiUrl}/user_info`, {
headers: {
...cookies
}
}).then(res => {
return res.data;
})
.then(data => {
setUser(data);
})
.catch(e => {
setUser(null);
})
} else {
setUser(null);
}
}, []);
return {
user,
signin,
signout
}
}
function getCookies() {
let cookies = document.cookie.split(';');
let authTokens = {
'access-token': null
};
for (const cookie of cookies) {
let cookiePair = cookie.split('=');
if (authTokens.hasOwnProperty(cookiePair[0].trim().toLowerCase()))
authTokens[cookiePair[0].trim()] = decodeURIComponent(cookiePair[1]);
}
return authTokens;
}
and then the dashboard component is the homepage. Nothing interesting.
The problem is when a user is in fact logged in (the access-token cookie is set as well as other tokens), they're still routed to the login page because of the fact that calling the API which checks that these tokens are valid is asynchronous, so the user is set to null initially.
What am I missing here? how can I wait until the API response is returned without blocking the user interface? Should I save user state in the redux state or is there some other work around?
Thanks a lot!
Like Jonas Wilms suggested, I added a loading state variable in user-auth similar to user and set it to true before each request and false after the request is completed.
In my App component, I changed the PrivateRoute function to show a loading spinner as long as the user state is loading. When it's set to false, I check whether the user is logged in or not and show the Dashboard component or redirect to login page accordingly.
function PrivateRoute({ children, ...rest }) {
let auth = useAuth();
return (
<Route
{...rest}
render={({ location }) =>
auth.loading ?
<Loading /> :
auth.user ? (
children
) : (
<Redirect
to={{
pathname: "/login",
state: { from: location }
}}
/>
)} />
)
}
I am trying to pass a variable from the state of a main App component to a component named ShoppingList as a prop. For some strange reason it is not getting passed even though it is definitely in the state of the of the App component according to React Dev Tools. However, a near identical component named Stocks receives their state variable as a prop and there is seemingly no difference between each one.
const App = () => {
const [stockChange, setStockChange] = useState(false)
const [shoppingListChange, setShoppingListChange] = useState(false)
const [user, setUser] = useState('')
const [loggedIn, setLoggedIn] = useState(false)
const [stocks, setStocks] = useState('')
const [shoppingList, setShoppingList] = useState('')
useEffect(() => {
const loggedInUserJson = window.localStorage.getItem('loggedInUser')
if (loggedInUserJson) {
const userToken = JSON.parse(loggedInUserJson)
setUser(userToken)
setLoggedIn(true)
fetchUserDetails()
} else {
setUser(null)
setLoggedIn(false)
}
}, [loggedIn])
const putShoppingList = async () => {
console.log('put shopping')
return await axios.put(`http://localhost:3001/api/shoppinglists/${shoppingList._id}`, shoppingList,
{
headers: {
'Authorization': `Bearer ${user.token}`
}
})
}
useEffect(() => {
console.log('shopping list change')
putShoppingList()
.then(() => fetchUserDetails())
}, [shoppingListChange])
const fetchUserDetails = async () => {
const res = await axios.get('http://localhost:3001/api/users', {
headers: {
'Authorization': `Bearer ${user.token}`
}
})
setStocks(res.data.stocks)
setShoppingList(res.data.shoppinglists)
}
const putStocks = async () => {
console.log('put stocks')
return await axios.put(`http://localhost:3001/api/stocks/${stocks._id}`, stocks,
{
headers: {
'Authorization': `Bearer ${user.token}`
}
})
}
useEffect(() => {
console.log('stock change')
putStocks()
.then(() => fetchUserDetails())
}, [stockChange])
const logOut = () => {
window.localStorage.removeItem('loggedInUser')
setLoggedIn(false)
}
const getName = () => {
if (user) {
return user.name
} else {
return ''
}
}
return (
<StyledPage>
<Topbar id='topbar' loggedIn={loggedIn} name={getName()} logOut={logOut} />
<Sidebar />
<StyledMain>
<Switch>
<Route path='/login'>
<Login setUser={setUser} setLoggedIn={setLoggedIn} />
</Route>
<Route path='/register' component={Register} />
<Route path='/stock'>
<Stock stocks={stocks} setStocks={setStocks} setStockChange={setStockChange} stockChange={stockChange} />
</Route>
<Route path='/shoppinglist'>
<ShoppingList
shoppingList={shoppingList}
setShoppingList={setShoppingList}
shoppingListChange={shoppingListChange}
setShoppingListChange={setShoppingListChange}
/>
</Route>
</Switch>
</StyledMain>
</StyledPage>
);
}
My only two ideas is that React Router is causing a problem or that the effect hooks are interfering somehow when they are first rendered.
You code has multiple bugs, especially with dependencies and reactions. You should use eslint-plugin-react-hooks.
const App = () => {
const [stockChange, setStockChange] = useState(false)
const [shoppingListChange, setShoppingListChange] = useState(false)
const [user, setUser] = useState('')
const [loggedIn, setLoggedIn] = useState(false)
const [stocks, setStocks] = useState('')
const [shoppingList, setShoppingList] = useState('')
useEffect(() => {
const loggedInUserJson = window.localStorage.getItem('loggedInUser')
if (loggedInUserJson) {
const userToken = JSON.parse(loggedInUserJson)
setUser(userToken)
setLoggedIn(true)
fetchUserDetails()
} else {
setUser(null)
setLoggedIn(false)
}
}, [])
const putShoppingList = useCallback(async () => {
console.log('put shopping')
return await axios.put(`http://localhost:3001/api/shoppinglists/${shoppingList._id}`, shoppingList,
{
headers: {
'Authorization': `Bearer ${user.token}`
}
})
}, [shoppingList._id, user.token]);
const fetchUserDetails = useCallback(async () => {
const res = await axios.get('http://localhost:3001/api/users', {
headers: {
'Authorization': `Bearer ${user.token}`
}
})
setStocks(res.data.stocks)
setShoppingList(res.data.shoppinglists)
}, [user.token]);
useEffect(() => {
console.log('shopping list change')
putShoppingList()
.then(() => fetchUserDetails())
}, [])
const putStocks = useCallback(async () => {
console.log('put stocks')
return await axios.put(`http://localhost:3001/api/stocks/${stocks._id}`, stocks,
{
headers: {
'Authorization': `Bearer ${user.token}`
}
})
}, [stocks._id, user.token]);
useEffect(() => {
console.log('stock change')
putStocks()
.then(() => fetchUserDetails())
}, [])
const logOut = () => {
window.localStorage.removeItem('loggedInUser')
setLoggedIn(false)
}
const getName = () => {
if (user) {
return user.name
} else {
return ''
}
}
return (
<StyledPage>
<Topbar id='topbar' loggedIn={loggedIn} name={getName()} logOut={logOut}/>
<Sidebar />
<StyledMain>
<Switch>
<Route path='/login'>
<Login setUser={setUser} setLoggedIn={setLoggedIn}/>
</Route>
<Route path='/register' component={Register} />
<Route path='/stock'>
<Stock stocks={stocks} setStocks={setStocks} setStockChange={setStockChange} stockChange={stockChange}/>
</Route>
<Route path='/shoppinglist'>
<ShoppingList
shoppingList={shoppingList}
setShoppingList={setShoppingList}
shoppingListChange={shoppingListChange}
setShoppingListChange={setShoppingListChange}
/>
</Route>
</Switch>
</StyledMain>
</StyledPage>
);
}