I use React for frontend and Node.js in backend and Postgre for database.
I have create my own API for authenticating user and using useState and useContext hook to store the login status of the user.
I also setup a Redirect function after successful login but the useState is taking a while to update the login status of user and because of that the page is not being redirect.
I tried using async and await while fetching the data from the server but still there is delay in authenticating the user.
I also tried to follow some blogs like this
This context state handle the login functionality and update the login status within the component.
import React, { createContext, useContext, useState } from "react";
import { GlobalContext } from "./GlobalState";
export const LoginAuth = createContext();
export const ProvideAuth = ({ children }) => {
const auth = useProvideAuth();
return <LoginAuth.Provider value={auth}>{children}</LoginAuth.Provider>;
};
export const useAuth = () => {
return useContext(LoginAuth);
};
const useProvideAuth = () => {
const [user, setUser] = useState(null);
const { setIsLogin } = useContext(GlobalContext);
const login = async (userDetails) => {
const response = await fetch("http://localhost:4000/api/v1/login/", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(userDetails),
}).catch((err) => console.log(err.message));
const data = await response.json();
if (data?.error) {
setUser(false);
} else {
setUser(data);
setIsLogin(true);
}
};
return { user, login };
};
This state used to update the login state throughout the app
import React, { createContext, useState } from "react";
export const GlobalContext = createContext();
export const GlobalProvider = ({ children }) => {
const [isLogin, setIsLogin] = useState(false);
return (
<GlobalContext.Provider value={{ isLogin, setIsLogin }}>
{children}
</GlobalContext.Provider>
);
};
Private Route Code
import React, { useContext } from "react";
import { Redirect, Route } from "react-router";
import { GlobalContext } from "../State/GlobalState";
const PrivateRouteLogin = ({ children, ...rest }) => {
const { isLogin } = useContext(GlobalContext);
return (
<Route
{...rest}
render={({ location }) => {
return isLogin ? (
children
) : (
<Redirect
to={{
pathname: "/login",
state: { from: location },
}}
></Redirect>
);
}}
/>
);
};
export default PrivateRouteLogin;
You can have an another value for your loginStatus
false = not logged in
true = logged in
pending = for collect all data
in this situation you can have a backdrop(loading) that show over your website till detect loginStatus
Related
I have a question about react router. I'm using different paths for the same component. The path is different since a changeable route parameter is given inside. So, does the change of the parameter causes the component to remount/unmount? If yes, does the remount of the component cause an initial render again? (I'm using functional components. I'm building a chatapp. There will be different chatrooms and the user can navigate to them from the home component.)
import './Home.css'
import {BrowserRouter as Router, Route, Switch, useHistory} from 'react-router-dom';
import { Link } from 'react-router-dom/cjs/react-router-dom.min';
import ChatRoom from './ChatRoom';
import { useEffect, useState } from 'react';
const Home = ({db, auth, user}) => {
const history = useHistory();
const [user1, setUser1] = useState('Loading...');
const logOut = () => {
auth.signOut().then(() => {
history.replace("/");
});
}
useEffect(() => {
db.collection('users').get().then(snapshot => {
snapshot.docs.forEach(doc => {
if (doc.id === user.uid) {
setUser1(doc.data().username);
}
})
}).catch(error => {
console.log(error);
})
}, [])
return (
<Router>
<div>
<Link to="/rooms/ChatA">ChatA</Link>
<Link to="/rooms/ChatB">ChatB</Link>
<Switch>
<Route path="/rooms/:ChatID">
<ChatRoom db={db} auth={auth}/>
</Route>
<Switch>
</div>
</Router>
)
}
export default Home;
This is the component where the user will navigate to particular chatroom from.
import React, { useState, useEffect, useRef } from 'react'
import { useParams } from 'react-router-dom';
import {firebase} from '../../Firebase/firebase';
const ChatRoom = ({ db, auth }) => {
const { chatID } = useParams();
const [user, setUser] = useState(null);
const [docs, setDocs] = useState(null)
const [msg, setMsg] = useState('');
const dummy = useRef();
const sendMsg = (e) => {
e.preventDefault();
db.collection(chatID).add({
username: user,
text: msg,
createdAt: firebase.firestore.FieldValue.serverTimestamp()
}).then(() => {
dummy.current.scrollIntoView({ behavior: 'smooth' });
})
setMsg('');
}
const deleteMsg = (id) => {
db.collection(chatID).doc(id).delete();
}
useEffect(() => {
auth.onAuthStateChanged(user => {
if (user) {
db.collection('users').get().then(snapshot => {
snapshot.docs.forEach(doc => {
if (doc.id === user.uid) {
setUser(doc.data().username);
}
})
})
}
})
db.collection(chatID).orderBy('createdAt').onSnapshot(snapshot)
=> {
setDocs(snapshot.docs);}
}, [])
return (
<div>
//some contents
</div>
);
}
export default ChatRoom;
This is the component which will be mounted according to which chatroom the user will navigate to.
I'll repeat my question: Will the change of the route parameter value cause a remount of the ChatRoom component and cause another initial render respectively?
I'm having problem with privateRoute it should be redirecting to the Home page after signin but it keeps redirecting to '/signin' page. everything is working fine if I remove PrivateRoute im not sure I'm beginner with react and it's my first time using firebase auth is there any way to fix this problem I'm sorry my code is messy
import React, {useEffect, useState} from 'react'
import firebase from 'firebase'
import StyledFirebaseAuth from 'react-firebaseui/FirebaseAuth'
import { Redirect, useHistory } from 'react-router-dom';
import axios from 'axios';
import { useAuth } from "../context/AuthContext";
import { Route, withRouter } from 'react-router-dom';
var uiconfig = {
signInFlow: 'popup',
signInSuccessUrl:'/',
signInOptions : [
firebase.auth.EmailAuthProvider.PROVIDER_ID],
callbacks: {
signInSuccessWithAuthResult: function(authResult, redirectUrl) {
return true;
}
}
};
const signOut = () => {
firebase.auth().signOut().then(function(){
return console.log('signout');
}).catch(() => {
console.error("error signout");
})
return <Redirect to='/signin'></Redirect>
}
var Cuser = null
const PrivateRoute = ({component: Component, ...rest}) => {
const [user, setUser] = useState(false)
const {currentUser} = useAuth()
console.log(Cuser)
return (
<Route {...rest} render={props => {
if(Cuser){
return <Component {...props}></Component>
} else {
return <Redirect to="/signin" />
}
}} ></Route>)
}
const Signup = () => {
const {history} = useHistory()
useEffect(() => {
const authOberver = firebase.auth().onAuthStateChanged((user) => {
setUser(user)
});
return authOberver
})
const [user, setUser] = useState(null)
if(user){
axios.post("http://localhost:4000/notes", {
email: user.email,
})
Cuser = true
}
return (
<>
<div>Signin / Register</div>
<StyledFirebaseAuth uiConfig={uiconfig} firebaseAuth={firebase.auth()}></StyledFirebaseAuth>
</>
)
}
export {signOut}
export default Signup;
export {PrivateRoute};
I tried to create a separate file for current user and using currentUser to check if the user is logged in or not but still happing the same problem
import React, { useContext, useEffect, useState } from "react";
import { auth } from "../firebase";
import firebase from "firebase";
import axios from 'axios'
import StyledFirebaseAuth from 'react-firebaseui/StyledFirebaseAuth'
const AuthContext = React.createContext();
export function useAuth() {
return useContext(AuthContext);
}
export function AuthProvider({ children }) {
const [currentUser, setCurrentUser] = useState();
useEffect(() => {
const unsubscribed = auth.onAuthStateChanged((user) => {
setCurrentUser(user);
})
return unsubscribed
}, []);
const value = {
currentUser,
};
return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
}
I think the problem is that you are using the variable Cuser to decide what component to render. So even if Cuser is set to true after signup, when the component gets re-rendered, it's value gets back to being null which is why it goes to else {return <Redirect to="/signin" />} block.
You can modify you AuthContext to store the login status in state. In below code, lets say cUser:
export function AuthProvider({ children }) {
const [currentUser, setCurrentUser] = useState();
cont [cUser, setCUser] = useState(false);
useEffect(() => {
const unsubscribed = auth.onAuthStateChanged((user) => {
setCurrentUser(user);
})
return unsubscribed
}, []);
const value = {
currentUser,
cUser: cUser,
setCUser: setCUser,
};
return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
}
And then use setCUser to set and check the value
import React, {useEffect, useState} from 'react'
import firebase from 'firebase'
import StyledFirebaseAuth from 'react-firebaseui/FirebaseAuth'
import { Redirect, useHistory } from 'react-router-dom';
import axios from 'axios';
import { useAuth } from "../context/AuthContext";
import { Route, withRouter } from 'react-router-dom';
var uiconfig = {
signInFlow: 'popup',
signInSuccessUrl:'/',
signInOptions : [
firebase.auth.EmailAuthProvider.PROVIDER_ID],
callbacks: {
signInSuccessWithAuthResult: function(authResult, redirectUrl) {
return true;
}
}
};
const signOut = () => {
firebase.auth().signOut().then(function(){
setCUser(false);
return console.log('signout');
}).catch(() => {
console.error("error signout");
})
return <Redirect to='/signin'></Redirect>
}
var Cuser = null
const PrivateRoute = ({component: Component, ...rest}) => {
const [user, setUser] = useState(false)
const {currentUser, cUser, setCUser} = useAuth()
console.log(Cuser)
return (
<Route {...rest} render={props => {
if(cUser){
return <Component {...props}></Component>
} else {
return <Redirect to="/signin" />
}
}} ></Route>)
}
const Signup = () => {
const {history} = useHistory()
useEffect(() => {
const authOberver = firebase.auth().onAuthStateChanged((user) => {
setUser(user)
});
return authOberver
})
const [user, setUser] = useState(null)
if(user){
axios.post("http://localhost:4000/notes", {
email: user.email,
})
setCUser(true);
}
return (
<>
<div>Signin / Register</div>
<StyledFirebaseAuth uiConfig={uiconfig} firebaseAuth={firebase.auth()}></StyledFirebaseAuth>
</>
)
}
export {signOut}
export default Signup;
export {PrivateRoute};
I am making a API call in useEffect Hook and then saving the data in redux store. From redux store I am storing data in my browser local storage by making a useSelector call to get data from redux store.
How to make useSelector to call only once the data is ready from API.
import React, { useEffect, useState } from "react";
import Navbar from "./Navbar";
import Footer from "./Footer";
import HomeMenu from './HomeMenu';
import { fetchingInPending, fetchingSuccess, fetchingFailed } from './HomeSlice';
import { useSelector, useDispatch } from 'react-redux';
const Home = () => {
const dispatch = useDispatch();
useEffect(() => {
(async () => {
dispatch(fetchingInPending());
const response = await fetch("https://localhost:44316/api/auth/getuser", {
headers: {
"Content-Type": "application/json"
},
credentials: "include",
});
if(response.status === 200){
const content = await response.json();
dispatch(fetchingSuccess({name: content.name, role: content.role}));
}
else{
dispatch(fetchingFailed());
}
})();
},[]);
localStorage.setItem('user', JSON.stringify(useSelector(state => state.userDetails)));
const user = localStorage.getItem('user');
console.log(user);
return (
<React.Fragment>
<h1>Home</h1>
</React.Fragment>
)};
HomeSlice
import { createSlice } from "#reduxjs/toolkit";
export const homeSlice = createSlice({
name: "userDetails",
initialState: {
name: "",
role: "",
isLoading: false
},
reducers: {
fetchingInPending: (state) => {
state.isLoading = true;
},
fetchingSuccess: (state, action) => {
state.name = action.payload.name;
state.role = action.payload.role;
state.isLoading = false;
state.error = "";
},
fetchingFailed: (state, action) => {
state.isLoading = false;
state.error = action.payload.error;
},
},
});
export const { fetchingInPending, fetchingSuccess, fetchingFailed } = homeSlice.actions;
export default homeSlice.reducer;
On my browser console I am getting data after three calls.
Index.js contain:
Index.js code:
import React from 'react';
import ReactDOM from 'react-dom';
import './style/index.css';
import App from './App';
import { BrowserRouter} from 'react-router-dom';
import store from './store';
import { Provider } from 'react-redux';
ReactDOM.render(
<React.StrictMode>
<BrowserRouter>
<Provider store={store}>
<App />
</Provider>
</BrowserRouter>
</React.StrictMode>,
document.getElementById('root')
);
I think #slideshowp2 is correct, but for incorrect reasoning. There are no asynchronous actions being dispatched (i.e. thunks or similar), so there are no returned Promises to be fulfilled.
Your code
Logs the initial state
Logs the state after dispatch(fetchingInPending());
Logs the third state update after dispatch(fetchingSuccess({ name: content.name, role: content.role }));
You can't conditionally call React hooks, so your code is correctly persisting each state update to localStorage.
If you want to conditionally persist data to localStorage then place that logic in another useEffect hook with a dependency on the redux state. The condition is ensuring that the values from your redux store have been populated.
const userDetails = useSelector(state => state.userDetails);
...
useEffect(() => {
const { name, role } = userDetails;
if (name && role) {
localStorage.setItem('user', JSON.stringify({ name, role }));
}
}, [userDetails]);
Alternatively you can just apply the localStorage persistence right in the existing useEffect. Here you persist to localStorage only when also dispatching the success action
useEffect(() => {
(async () => {
dispatch(fetchingInPending());
const response = await fetch("https://localhost:44316/api/auth/getuser", {
headers: {
"Content-Type": "application/json"
},
credentials: "include",
});
if(response.status === 200){
const { name, role } = await response.json();
const userDetails = { name, role };
dispatch(fetchingSuccess(userDetails)); // <-- dispatch user details
localStorage.setItem( // <-- persist user details
'user',
JSON.stringify(userDetails)
);
}
else{
dispatch(fetchingFailed());
}
})();
},[]);
i'm practicing reactjs watching this video https://www.youtube.com/watch?v=5rh853GTgKo&list=PLJRGQoqpRwdfoa9591BcUS6NmMpZcvFsM&index=9
I want to verify my information using uid and token, but I don't know how to deliver it.
In this code: Activate.js in container
import React, { useState } from 'react';
import { Redirect } from 'react-router-dom';
import { connect } from 'react-redux';
import { verify } from '../actions/auth';
const Activate = ({ verify, match }) => {
const [verified, setVerified] = useState(false);
const verify_account = e => {
const uid = match.params.uid; // I Think This part is Problem
const token = match.params.token;
verify(uid, token);
setVerified(true);
};
if (verified) {
return <Redirect to='/' />
}
and this code : auth.js in actions
export const verify = (uid, token) => async dispatch => {
const config = {
headers: {
'Content-Type': 'application/json'
}
};
const body = JSON.stringify({ uid, token });
try {
await axios.post(`${process.env.REACT_APP_API_URL}/auth/users/activation/`, body, config);
dispatch ({
type: ACTIVATION_SUCCESS,
});
} catch (err) {
dispatch ({
type: ACTIVATION_FAIL
});
}
}
i think i didn't render uid, token but i confused how to do that
App.js code:
<Router>
<Layout>
<Switch>
<Route exact path ='/activate/:uid/:token'>
<Activate />
</Route>
</Switch>
</Layout>
</Router>
I'd appreciate any help. :)
use the useParams hook to extract uid and token params:
import React, { useState } from 'react';
import { Redirect, useParams } from 'react-router-dom';
import { connect } from 'react-redux';
import { verify } from '../actions/auth';
const Activate = ({ verify }) => {
const [verified, setVerified] = useState(false);
const { uid, token } = useParams();
const verify_account = e => {
verify(uid, token);
setVerified(true);
};
if (verified) {
return <Redirect to='/' />
}
I just started playing with context today and this is my usercontext
import { createContext, useEffect, useState } from "react";
import axios from "axios";
export const userContext = createContext({});
const UserContext = ({ children }) => {
const [user, setUser] = useState({});
useEffect(() => {
axios.get("/api/auth/user", { withCredentials: true }).then((res) => {
console.log(res);
setUser(res.data.user);
});
}, []);
return <userContext.Provider value={user}>{children}</userContext.Provider>;
};
export default UserContext;
this is how im using it in any component that needs the currently logged in user
const user = useContext(userContext)
my question is whenever the user logs in or logs out I have to refresh the page in order to see the change in the browser. is there any way that I can do this where there does not need to be a reload. also any general tips on react context are appreciated
(EDIT)
this is how Im using the UserContext if it helps at all
const App = () => {
return (
<BrowserRouter>
<UserContext>
<Switch>
{routes.map((route) => (
<Route
key={route.path}
path={route.path}
component={route.component}
/>
))}
</Switch>
</UserContext>
</BrowserRouter>
);
};
Where is your context consumer?
The way it is set up, any userContext.Consumer which has a UserContext as its ancestor will re render when the associated user is loaded, without the page needing to be reloaded.
To make it clearer you should rename your UserContext component to UserProvider and create a corresponding UserConsumer component:
import { createContext, useEffect, useState } from "react";
import axios from "axios";
export const userContext = createContext({});
const UserProvider = ({ children }) => {
const [user, setUser] = useState({});
useEffect(() => {
axios.get("/api/auth/user", { withCredentials: true }).then((res) => {
console.log(res);
// setting the state here will trigger a re render of this component
setUser(res.data.user);
});
}, []);
return <userContext.Provider value={user}>{children}</userContext.Provider>;
};
const UserConsumer = ({ children }) => {
return (
<userContext.Consumer>
{context => {
if (context === undefined) {
throw new Error('UserConsumer must be used within a UserProvider ')
}
// children is assumed to be a function, it must be used
// this way: context => render something with context (user)
return children(context)
}}
</userContext.Consumer>
);
};
export { UserProvider, UserConsumer };
Usage example:
import { UserConsumer } from 'the-file-containing-the-code-above';
export const SomeUiNeedingUserInfo = props => (
<UserConsumer>
{user => (
<ul>
<li>{user.firstName}</>
<li>{user.lastName}</>
</ul>
)}
</UserConsumer>
)
To be fair, you could also register to the context yourself, this way for a functional component:
const AnotherConsumer = props => {
const user = useContext(userContext);
return (....);
}
And this way for a class component:
class AnotherConsumer extends React.Component {
static contextType = userContext;
render() {
const user = this.context;
return (.....);
}
}
The benefit of the UserConsumer is reuasability without having to worry if you're in a functional or class component: it will used the same way.
Either way you have to "tell" react which component registers (should listen to) the userContext to have it refreshed on context change.
That's the whole point of context: allow for a small portion of the render tree to be affected and avoid prop drilling.