I have built a simple app with Nextjs and Firebase (right now, I'm only using the authentication providing by Firebase). I made a login component and a greetingUser component whose roles are defined as follow :
greetingUser : display the name of the user currently logged in and a greeting message;
// greetingUse.js
import React, { useContext } from 'react';
import { UserContext } from './User/User';
export default function GreetUser(){
const user = useContext(UserContext);
return (
<div>
<p>hello, {user.user.name} !</p>
</div>
)
}
login : there are two buttons (login and logout). When the user log in, I want to receive the name of the user (user.displayName) and update the greetingUser component to display that name.
// login.js
import firebase from 'firebase/app';
import React, { useContext, useReducer } from 'react';
import { UserContext } from './User/User';
import { auth } from '../utils/firebase';
export default function Login() {
const provider = new firebase.auth.GoogleAuthProvider();
const state = useContext(UserContext);
console.log(state);
console.log(state.user);
const updateUserDetails = () => {
auth.onAuthStateChanged(user => {
if (user) {
return user.displayName;
} else {
return "You are logged out";
}
})
console.log(state.user);
}
return (
<>
<div className="btn-group">
<button
id="logInBtn"
className="btn btn-primary"
onClick={() => auth.signInWithPopup(provider) && state.setUser(updateUserDetails())}>
Login
</button>
<button
id="logOutBtn"
className="btn btn-danger"
onClick={() => auth.signOut()}>
Logout
</button>
</div>
<p>hello {state.user.name}</p>
</>
)
}
You'll notice that I'm using react context. After some research, that the solution I found. Here is the Provider code :
// User.js
import React, { createContext, useState } from 'react';
const UserContext = createContext({
user: {name: "User"},
setUser: () => {}
});
const UserContextProvider = (props) => {
const setUser = (user) => {
setState({...state, user: user})
}
const initState = {
user: {name: "User"},
setUser: setUser
}
const [state, setState] = useState(initState);
return (
<UserContext.Provider value={state}>
{props.children}
</UserContext.Provider>
)
};
export { UserContext, UserContextProvider };
The problem is when logging in, the value of the user object is undefined resulting in an error that prevent the page from rendering :
login.js?4f9b:42 Uncaught TypeError: Cannot read property 'name' of undefined at Login (login.js?4f9b:42)
I thing it's because while authenticating, the value of user is unknow, but after the authentification is completed, the value of user is not updated.
I did some research, but I don't find a solution adapted to my problem.
Related
I am following a tutorial on how to integrate Stripe payments in React. The problem I am having is for this page where it will check if a user is logged in and if they are logged in it will show a cookie and if they are not it will show a upgrade to premium button. All is working however there is an annoying behaviour where if I refresh the page it briefly shows the 'Upgrade to premium' button before checking the user is premium and then quickly change to the cookie. I want it to ideally do the check before the content is rendered so it's a bit smoother and doesn't look jittery.
import React, { useEffect,useState } from 'react'
import firebase from "../src/firebase";
import { useAuthState } from "react-firebase-hooks/auth";
import { getAuth, createUserWithEmailAndPassword, signInWithEmailAndPassword, signOut,onAuthStateChanged } from "firebase/auth";
import createCheckoutSession from "./stripe/createCheckoutSession";
import usePremiumStatus from "./stripe/usePremiumStatus";
import Login from "./Login";
import { Link, useNavigate } from 'react-router-dom'
import { Spinner } from 'react-bootstrap';
export default function Practise() {
const auth = getAuth();
const {currentUser}=getAuth()
const userIsPremium = usePremiumStatus(currentUser);
const [load,setLoading]=useState(false)
const debugging = async ()=>{
const {currentUser}= await getAuth().then(()=>{
console.log(currentUser)
})
}
const [loggedIn,setLoggedIn]= useState(false)
let navigate = useNavigate();
const handleNewInfo = ()=>{
onAuthStateChanged(auth, (user) => {
if (user) {
const uid = user.uid;
setLoggedIn(true)
const prom = async ()=>{
await ((user.getIdTokenResult()))
}
prom()
console.log(userIsPremium)
} else {
navigate('/login')
console.log('not logged in!')
}
})}
const getUser = ()=>{
console.log(currentUser)
}
useEffect(
handleNewInfo
,[]
) ;
const handleClick = (userid)=>{
setLoading(true)
createCheckoutSession(userid)
}
return (
<div>
{!loggedIn && <h1>Loading...</h1>}
{loggedIn && (
<div>
<h1>Hello, {currentUser.displayName}</h1>
{!userIsPremium && (
<button onClick={() => handleClick(currentUser.uid)}>
Upgrade to premium!
</button>
) }
{!userIsPremium && load && (
<Spinner animation="border" role="status">
<span className="visually-hidden">Loading...</span>
</Spinner>
) }
{userIsPremium && <h2>Have a cookie 🍪 Premium customer!</h2>
}
</div>
)}
</div>
);
}
I'm doing a project where the first screen is a simple input to put the name on, and then it goes to another screen (another component) where I need to use the value that the user put in the input earlier to show custom content. I tried to do it with Redux but I'm having difficulties to store the input value in the Redux Store and then use that value in another component. I would like to know how I could store this value and then use it in another component (I honestly have no idea how to do it). If anyone wants, I can also show the other component code.
my first component (where user puts his name):
import React, {useState, useEffect} from "react";
import "../_assets/signup.css";
import "../_assets/App.css";
import { Link } from 'react-router-dom';
function Signup() {
const [name, setName] = useState('')
const [buttonGrey, setButtonGrey] = useState('#cccccc')
useEffect(() => {
if(name!== '') {
setButtonGrey("black")
} else {
setButtonGrey('#cccccc')
}
}, [name])
const handleSubmitForm= (e) => {
e.preventDefault()
store.dispatch({
type: 'SAVE_USER',
payload: name,
})
console.log({name})
}
const handleChangeName = (text) => {
setName(text)
}
return (
<div className="container">
<div className="LoginBox">
<form onSubmit={handleSubmitForm}>
<h2>Welcome to codeleap network</h2>
<text>Please enter your username</text>
<input
type="text"
name="name"
value={name}
onChange = {e => handleChangeName(e.target.value)}
placeholder="Jane Doe"
/>
<div className="button">
<Link to="/main">
<button
type="submit"
style={{backgroundColor: buttonGrey}}
disabled={!name}
>
ENTER
</button>
</Link>
</div>
</form>
</div>
</div>
);
}
export default Signup;
my store.js:
import { createStore } from 'redux';
const reducer = (state= (''), action) => {
switch(action.type) {
case 'SAVE_USER': {
state = {...state, name: action.payload}
}
default: return state
}
}
const store = createStore(reducer)
export {store}
Heading
First, I suggest using Redux-Toolkit. It makes standing up and configuring a React redux store almost too easy.
Here's the quick-start guide
Create/convert to a state slice. When you create a slice you are declaring the name of the slice of state, the actions, and the reducer functions at the same time all at once.
import { createSlice } from "#reduxjs/toolkit";
const userSlice = createSlice({
name: "user",
initialState: "",
reducers: {
saveUser: (state, action) => action.payload
}
});
Create and configure the store.
import { configureStore } from "#reduxjs/toolkit";
import userSlice from "../path/to/userSlice";
const store = configureStore({
reducer: {
user: userSlice.reducer
}
});
Render a Provider and pass the store prop.
import { Provider } from "react-redux";
<Provider store={store}>
... app component ...
</Provider>
From here it's a matter of importing the dispatch function and selecting state, use useDispatch and useSelector from react-redux for this.
Signup
import { useDispatch } from "react-redux";
function Signup() {
const dispatch = useDispatch(); // <-- dispatch function
const navigate = useNavigate();
const [name, setName] = useState("");
const handleSubmitForm = (e) => {
e.preventDefault();
dispatch(userSlice.actions.saveUser(name)); // <-- dispatch the action
navigate("/main");
};
const handleChangeName = (text) => {
setName(text);
};
return (
...
);
}
Example Main component:
import { useSelector } from "react-redux";
const Main = () => {
const user = useSelector((state) => state.user); // <-- select the user state
return (
<>
<h1>Main</h1>
<div>User: {user}</div>
</>
);
};
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
I am initializing a TS React App connected to Firebase with a private route when the user is logged in.
Everything seems to work well until I refresh the page. When I do that, the app takes me back to the public route which is a login page.
I think that the problem could be the initial state of the context which is set to null, but maybe it's a different problem.
Here is the code for my user context:
import React, { useContext, useState, useEffect } from "react";
import firebase from "firebase/app";
import { auth } from "../firebase";
export const AuthContext = React.createContext<firebase.User | null>(null);
export function useAuth() {
return useContext(AuthContext);
}
export const AuthProvider: React.FC = ({ children }) => {
const [user, setUser] = useState<firebase.User | null>(null);
useEffect(() => {
const unsubscribe = auth.onAuthStateChanged((firebaseUser) => {
setUser(firebaseUser);
});
return unsubscribe;
}, []);
return <AuthContext.Provider value={user}>{children}</AuthContext.Provider>;
};
Here is how I created the private route:
import React, { useContext } from "react";
import { Redirect, Route, RouteProps } from "react-router";
import { AuthContext } from "../contexts/AuthContext";
interface IPrivateRoute extends RouteProps {
component: any;
}
const PrivateRoute = ({ component: Component, ...rest }: IPrivateRoute) => {
const user = useContext(AuthContext);
setTimeout(() => console.log(user), 1000);
return (
<div>
<Route
{...rest}
render={(props) => {
return user ? <Component {...props} /> : <Redirect to="/login" />;
}}>
</Route>
</div>
);
};
export default PrivateRoute;
I will be grateful for all the helpful answers!
I think you need to save the session to stay authenticated. Whenever you refresh the page your user state will be null.
What worked for me is I used JWT. https://jwt.io/
After user logs in successfully my server sends the user a token and I save the token in user's cookies. For each PrivateRoute the user requests they will send the token back to server for verification. If the verification is successful then return the PrivateRoute to them.
Background
I am working on a very routine chunk of code, I have created actions and reducers many times throughout my app. I am now setting up authentication, and have two containers loading based on routes / & /register.
Issue
I am trying to dispatch an action, and do a simple console.log("test"). I have done this many times before, in-fact, I have literally duplicated a container and altered the names of the dispatched action names. One container works, while the other is hitting me with:
Uncaught TypeError: _this2.propsregisterHandler is not a function
I am confused why its not showing a . between props and registerHandler
Here is the relevent code:
Container Import
import { register } from "../../store/actions/authentication";
JSX
<div
className="btn btn-primary col"
onClick={() =>
this.props.registerHandler(
this.state.email,
this.state.password
)
}
>
Register
</div>
....
Disptach Code
const mapStateToProps = state => {
return {};
};
const mapDisptachToProps = dispatch => {
return {
registerHandler: () => dispatch(register())
};
};
export default connect(
mapStateToProps,
mapDisptachToProps
)(Register);
The action
import * as actionTypes from "./actiontypes";
export const register = () => {
console.log("TEST");
return { type: actionTypes.REGISTER };
};
Reducer
const reducer = (state = initialState, action) => {
switch (action.type) {
case actiontypes.REGISTER: {
console.log("you called the reducer");
return state;
}
Revised
This code here does not work, I always get the error, however if I call the same action in my login component, it will work.
import React, { Component } from "react";
import { connect } from "react-redux";
import { registerUserToApp } from "../../store/actions/authentication";
import "../Login/login";
export class Register extends Component {
state = {
email: "",
password: ""
};
render() {
return (
<div
className="btn btn-primary"
onClick={() => {
this.props.registerUserToAppHandler();
}}
>
Register
</div>
);
}
}
const mapStateToProps = state => {
return {};
};
const mapDispatchToProps = dispatch => {
return {
registerUserToAppHandler: () => dispatch(registerUserToApp())
};
};
export default connect(
mapDispatchToProps,
mapStateToProps
)(Register);
login Component
import React, { Component } from "react";
import { connect } from "react-redux";
import Aux from "../../components/hoc/Aux";
import Logo from "../../assets/images/Logo.png";
import GoogleLogo from "../../assets/images/google.svg";
import {
loginUser,
loginUserWithGoogle,
registerUserToApp
} from "../../store/actions/authentication";
import "./login.css";
export class Login extends Component {
state = {
email: "",
password: ""
};
render() {
const userNameChangeHandler = event => {
this.setState({
email: event.target.value
});
};
const passworChangeHandler = event => {
this.setState({
password: event.target.value
});
};
return (
<Aux>
...
<div
className="btn btn-primary col"
onClick={() => {
this.props.loginUserHandler(
this.state.email,
this.state.password
);
this.props.registerUserToAppHandler();
}}
>
Sign In
</div>
...
</Aux>
);
}
}
const mapStateToProps = state => {
return {};
};
const mapDisptachToProps = dispatch => {
return {
loginUserHandler: (email, password) => dispatch(loginUser(email, password)),
registerUserToAppHandler: () => dispatch(registerUserToApp()),
loginUserWithGoogleHandler: () => dispatch(loginUserWithGoogle())
};
};
export default connect(
mapStateToProps,
mapDisptachToProps
)(Login);
I can't leave a comment, but shouldn't you add .css extension when importing styles?
import "../Login/login";
The issue was due to how I was loading this component into my container. I am nut sure of the exact reasoning but I was importing my component into the container using a named import import {Login} from ".../path", whereas it should have been import Login from ".../path".