React component not updating when state is changed - javascript

I am having an issue with my navigation not being updated when a user is being logged in.
So my a portion of my App.js looks like so;
import React from "react";
import { BrowserRouter as Router, Route } from "react-router-dom";
import Home from './views/Home';
import Login from './views/Login';
import Navigation from './components/Navigation';
function App() {
return (
<Router>
<React.Fragment>
<header className="header-global">
<Navigation />
</header>
<Route path="/" exact component={Home} />
<Route path="/login" component={Login} />
<Footer />
</React.Fragment>
</Router>
);
}
export default App;
In my Login.js file, I make a call to my API, check for valid details, if they are valid the response sets a token and also a expires at value into localStorage and then redirects to the homepage;
.then((response) => {
// Set token here
// set expires at here
// redirect to homepage after successful login
this.props.history.push("/");
This all works flawless, but in my Navigation.js file I want to change the link from Login to Logout but after the redirect it doesnt automatically do that .. I have to actually reload the page to see the Login change to Logout.
I have a seperate file called auth.js which has a function called isAuthenticated, this basically just checks that the token is there and valid and returns either true or false based on this.
This is my Navigation.js file (which i have cut down to only display whats needed), but i dont see anything wrong with it and I cant understand why its not automatically changing the value after the redirect.
import React, { Component } from 'react';
import auth from '../auth';
class Navigation extends Component {
constructor(props) {
super(props);
this.state = {
isAuthenticated: false,
};
}
componentDidMount() {
if ( auth.isAuthenticated() ) {
this.setState({ isAuthenticated: true })
}
}
render() {
const { isAuthenticated } = this.state;
return (
{ isAuthenticated ? 'Logout' : 'Login' }
);
}
}
export default Navigation;
I have also tried using the componentWillMount method as well.
The following is the auth.isAuthenticated code (cut down)
isAuthenticated () {
if (this.isExpired()) {
return false
}
if (!this.getToken()) {
return false
}
return true
}

You need to add props history for Navigation:
import React from "react";
import { BrowserRouter as Router, Route } from "react-router-dom";
import Home from './views/Home';
import Login from './views/Login';
import Navigation from './components/Navigation';
function App() {
return (
<Router>
<React.Fragment>
<header className="header-global">
<Navigation />
</header>
<Route path="/" exact component={Home} />
<Route path="/login" component={Login} />
<Footer />
</React.Fragment>
</Router>
);
}
export default App;
and in componentDidMount add this.props.history.push("/"):
import React, { PureComponent } from 'react';
import auth from '../auth';
class Navigation extends PureComponent {
constructor(props) {
super(props);
this.state = {
isAuthenticated: false,
};
}
componentDidMount() {
if ( auth.isAuthenticated() ) {
this.setState({ isAuthenticated: true });
this.props.history.push("/");
}
}
render() {
const { isAuthenticated } = this.state;
return (
{ isAuthenticated ? 'Logout' : 'Login' }
);
}
}
export default withRouter(Navigation);
Example:

Related

Redirecting to another page(route) from a React class component

I need some help to solve the following issue with using React.
In some web app I have a landing page, where I want to redirect the user to the login page in case she or he is not logged in.
I want to use the following landing page (taken from some tutorial I found on the net) in order to use it as a model for mine.
The problem is that this is a function component while my landing page is a class component. According to what I understand I guess I need to consider the code inside useEffect and (somewhat) transfer it to componentDidMount() in my class component. But I don't know how to do that. history.replace will not work in a class component (no Hooks in Classes). Any advice from a more React experienced user will be very welcome.
import React, { useEffect, useState } from "react";
import { useAuthState } from "react-firebase-hooks/auth";
import { useHistory } from "react-router";
import "./Dashboard.css";
import { auth, db, logout } from "./firebase";
....
function Dashboard() {
const [user, loading, error] = useAuthState(auth);
const [name, setName] = useState("");
const history = useHistory();
....
useEffect(() => { // Important part for my question !
if (loading) return;
if (!user) return history.replace("/");
....
}, [user, loading]);
return (
<div>
{/*...*/}
<button className="dashboard__btn" onClick={logout}>
Logout
</button>
</div>
);
}
export default Dashboard;
Here is what I tried on my Class Component:
class MyCompo extends React.Component {
constructor(props) {
super(props);
}
componentDidMount() {
console.log("--componentDidMount(MyCompo)--");
const { history } = this.props
history.push("/login");
}
.....
}
But I get the following error:
TypeError: history is undefined
componentDidMount
=============== Added information ===============
Below is the relevant part of the code I have been working on:
This part is what works:
<Route exact path="/" component={TopMenu}>
{true && <Redirect to="/login" />}
</Route>
What I tried in the Links Component did not work.
The code:
....
ReactDOM.render(
<Router>
<Switch>
<Route exact path="/" component={TopMenu}>
{true && <Redirect to="/login" />}
</Route>
<Route exact path="/login" component={Login} />
<Route exact path="/section1" component={Section1Page}/>
<Route exact path="/section2" component={Section2Page}/>
<Route exact path="/section3" component={Section3Page}/>
</Switch>
</Router>,
document.getElementById('root')
);
....
const TopMenu = () => {
return (
<div className='page_container'>
<Title/>
<Links path='/'/>
<button className="dashboard__btn" onClick={logout}>
Logout
</button>
</div>
)
};
class Links extends React.Component {
constructor(props) {
super(props);
}
componentDidMount() {
console.log("--componentDidMount(Links)--");
// This is some code I tried with no success.
const { history } = this.props
//history.push("/login");
}
componentDidUpdate(prevProps, prevState) {
console.log("--componentDidUpdate(Links)--");
}
render() {
return (
<div className='links_container'>
{(this.props.path != '/mng') &&
<React.StrictMode>
<Link to='/mng'>{mnMgrStr()}</Link><br/>
</React.StrictMode>}
{(this.props.path != '/other') &&
<React.StrictMode>
<Link to='/other'>{otherInpStr()}</Link><br/>
</React.StrictMode>}
.......
</div>
)
}
}
Following the example on the React Router docs you can use withRouter if your component isn't already receiving the route props, otherwise you can access history from the props.
class MyComponent extends React.Component {
...
componentDidMount() {
const { history } = this.props
// do whatever with history here
}
...
}
In react-router-dom version 5 there are a couple ways a class component can access the history object.
Rendered directly by a Route component via the component, or render or children function props so route props (i.e. history, location, and match) are passed.
component: <Route path="....." component={MyCompo} />
render: <Route path="....." render={routeProps => <MyCompo {...routeProps} />} />
Access the history object from the passed route props:
class MyCompo extends React.Component {
componentDidMount() {
const { history } = this.props;
history.push("/login");
}
...
}
Decorated by the withRouter Higher Order Component so the route props are injected.
import { withRouter } from 'react-router-dom';
class MyCompo extends React.Component {
componentDidMount() {
const { history } = this.props;
history.push("/login");
}
...
}
export default withRouter(MyCompo);
Well I hope by answering this question I can save lot of time of others. Don't need to panic it's not a major issue. I will explain step by step reason and solution.
First of all why this happening is
In react-router-dom **V6 (version 6) latest ** there is no history export or redirect.
There is navigate construct.
So to achieve in functional component there is useNavigate() hook.
Now coming to answer...
To redirect in class component using react-router-dom V6 we have to use component.
So now one has to follow the following steps:
Import navigate
import { Navigate } from "react-router-dom";
Use Navigate to redirect
So above I discussed syntax to do so now coming to your exact problem
You have to redirect user to login if he is not logged in
You can follow these steps:
create state to store status of user like logged in or not (inside constructor of class)
this.state = {
userLogged: false,
};
in your render method you have to add some condition like if user is not logged in take user to login page. see below..
render() {
const { userLogged } = this.state;
if (goToPay) {
return (
<Navigate to="/cart" state={selectedTiffin} props={selectedTiffin} />
);
}
}
That's it.
It can be confusing so I am giving full example so you can save your lot of time..
import React from "react";
import { Navigate } from "react-router-dom";
class Solve extends React.Component {
constructor(props) {
super(props);
this.state = {
userLogged: false,
};
}
// here you can write code to set the status of user like logged in or not
render() {
const { userLogged } = this.state;
if (userLogged ) {
return (
<Navigate to="/cart" />
);
}
return (
<>
Here you can return your original component that should be render when user is log in
</>
);
}
}
I hope this will help and work. Thank You

Warning: Can't perform a React state update on an unmounted component when login with auth0

I am following a tutorial from https://auth0.com/blog/role-based-access-control-rbac-and-react-apps/ and it seems that author doesn't support it anymore.
The idea is simple: Once a user presses Login button (on Header.js) he is redirected to auth0 page. There he enters his data and is redirected back to localhost:3000/callback route. This is when handleAuthentication is triggered.
Unfortunately, I am facing an issue when setting the state when the setSession function is used.
Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in the componentWillUnmount method.
in Auth (created by App)
in App
Here are the components:
App.js
import React from 'react'
import { Switch, Route, BrowserRouter as Router } from 'react-router-dom'
import Auth from './Auth';
import CallbackPage from "../pages/callback";
class App extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<Auth>
<div style={{width: '1280px', margin: '0 auto'}}>
<Router>
<Switch>
<Route exact path='/' component={HomePage} />
<Route path='/callback' component={CallbackPage} />
</Switch>
</Router>
</div>
</Auth>
)
}
}
export default App;
Auth.js
import React, {Component} from 'react';
import auth0 from 'auth0-js';
import {AUTH_CONFIG} from '../auth0-variables';
import {AuthProvider} from '../authContext';
const auth = new auth0.WebAuth({
domain: AUTH_CONFIG.domain,
clientID: AUTH_CONFIG.clientId,
redirectUri: AUTH_CONFIG.callbackUrl,
audience: `https://${AUTH_CONFIG.domain}/userinfo`,
responseType: 'token id_token'
});
class Auth extends Component {
state = {
authenticated: false,
}
initiateLogin = () => {
auth.authorize();
};
handleAuthentication = () => {
auth.parseHash((error, authResult) => {
if (error) {
console.log(error);
console.log(`Error ${error.error} occured`);
return
}
this.setSession(authResult);
})
};
setSession(authResult) {
this.setState({
// This does not update the state!!
authenticated: true,
});
};
render() {
const authProviderValue = {
...this.state,
initiateLogin: this.initiateLogin,
handleAuthentication: this.handleAuthentication,
};
return (
<AuthProvider value={authProviderValue}>
{this.props.children}
</AuthProvider>
)
}
};
export default Auth;
Header.js (Can component can be found at https://github.com/itaditya/react-rbac-auth0-article-code/blob/master/src/components/Can.js)
import React, { useEffect, useReducer } from 'react';
import {
BrowserRouter as Router,
Link }
from 'react-router-dom';
import Login from '../Login';
import Logout from '../Logout';
import Can from '../Can';
import { AuthConsumer } from '../../authContext';
const Header = (props) => {
return (
<AuthConsumer>
{({ user }) => (
<Can role={user.role} perform='home-page:seeLogin'
yes={() => (
<Login />
)}
no={() => (
<Logout />
)}
/>
</AuthConsumer>
)
}
export default Header
And pages:
homePage:
import React from 'react';
const HomePage = () => {
return (
<div>
<Header />
</div>
)
};
export default HomePage;
Callback page
import React from 'react';
import { Redirect } from 'react-router-dom';
import { AuthConsumer } from '../authContext';
const Callback = props => (
<AuthConsumer>
{({ handleAuthentication }) => {
if (/access_token|id_token|error/.test(props.location.hash)) {
handleAuthentication();
}
return <Redirect to='/' />;
}}
</AuthConsumer>
);
export default Callback;
Any help would be appreciated.

AuthStateChange React

I'm trying see if there is a user with firebase but every time I login into my app and console.log the user it says that there is no user and I can't log into my app.
import React,{Component} from 'react';
import SignUp from './components/Authentication/SignUp';
import {BrowserRouter as Router,Switch,Route,Link} from 'react-router-dom';
import Feed from './components/Feed/Feed';
import Search from './components/SearchUser/Search';
import Profile from './components/Profile/Profile';
import Header from './components/Header/Header';
import firebase from './util/firebase';
class App extends Component
{
constructor(){
super();
this.state={
user:''
}
}
componentDidMount(){
firebase.auth().onAuthStateChanged(function(user) {
if (user) {
// User is signed in.
this.setState({
user:user
})
// ...
} else {
// User is signed out.
// ...
}
});
}
render(){
const {user}=this.state;
console.log(user);
return(
<div>
{
user?(
<Router>
<Header/>
<Switch>
<Route exact path="/feed">
<Feed user={user}/>
</Route>
<Route path="/search">
<Search/>
</Route>
<Route path="/profile">
<Profile/>
</Route>
</Switch>
</Router>
):(
<SignUp/>
)
}
</div>
)
}
}
export default App;
Problem: there is no user because react is rendering the component before onAuthStateChanged's changes in your state so when it renders user is null.
Solution: (Probably not the best approach):
show the fall back UI while it's updating the State.
I'm sharing my solution by changing your code.
It worked for me when I faced the same issue.
import React,{Component} from 'react';
import SignUp from './components/Authentication/SignUp';
import {BrowserRouter as Router,Switch,Route,Link} from 'react-router-dom';
import Feed from './components/Feed/Feed';
import Search from './components/SearchUser/Search';
import Profile from './components/Profile/Profile';
import Header from './components/Header/Header';
import firebase from './util/firebase';
class App extends Component
{
constructor(){
super();
this.state={
user:'',
loading: true
}
}
componentDidMount(){
firebase.auth().onAuthStateChanged(function(user) {
if (user) {
// User is signed in.
this.setState({
user:user,
loading: false
})
// ...
} else {
// User is signed out.
// ...
}
});
}
render(){
const {user, loading}=this.state;
console.log(user);
if(loading) return <div>loading...</div>
return(
<div>
{
user?(
<Router>
<Header/>
<Switch>
<Route exact path="/feed">
<Feed user={user}/>
</Route>
<Route path="/search">
<Search/>
</Route>
<Route path="/profile">
<Profile/>
</Route>
</Switch>
</Router>
):(
<SignUp/>
)
}
</div>
)
}
}

React PrivateRouter Implementation not working correctly

I have a small react app which has user login, I want to have a private route which will be protected, and only authenticated users can use it.
Here is my code.
App.js
import React, { Component } from 'react';
import { BrowserRouter as Router, Route, Switch } from "react-router-dom";
// Styling
import './App.css';
// Privat route
import PrivateRoute from './routes/PrivateRoute';
// Common
import Navigation from './components/common/Navigation';
// Components
import Layout from './components/Layout'
import Login from './components/Login';
import Home from './components/Home';
import Profile from './components/Profile';
export default () => (
<Router>
<Switch>
<Layout>
{/* Public routes */}
<Route exact path="/" component={ Home } />
<Route exact path="/login" component={ Login } />
{/* Private routes */}
<PrivateRoute exact path="/profile" component={ Profile } />
</Layout>
</Switch>
</Router>
);
PrivateRoute.js
import React, { Component } from 'react';
import { Route, Redirect } from 'react-router-dom';
import UserService from '../services/User';
export default class PrivateRoute extends React.Component {
state = {
isAuthenticated: null,
user: null
}
async componentDidMount(){
let isAuthenticated, user = null;
try{
user = await UserService.auth();
isAuthenticated = true;
}catch(e){
isAuthenticated = false;
}
this.setState({ isAuthenticated, user });
}
render() {
const { isAuthenticated, user } = this.state;
if(isAuthenticated === null){
return null;
}
return (
isAuthenticated ?
<Route path={this.props.path} component={this.props.component}/> :
<Redirect to={{ pathname: '/login', state: { from: this.props.location }}} />
);
}
}
Home.js
import React, { Component } from 'react';
export default class Home extends Component{
render(){
return (
<div>Home!</div>
);
}
}
I am constantly getting the error
Warning: You tried to redirect to the same route you're currently on: "/login"
even though I am in the home page which is has no business checking if user is authenticated.
I suspect this issue has something to do with my routes and the common layout I am trying to implement, but I cant find the problem.

React-Router noMatch causes asteroid.userId to return falsey

Notice the line Users.methods.isAuthed() and you can see that its calling !!asteroid.userId . My login form works fine, but I'm trying to make a react-router authorization wall which means the router will load the component when the authorization check returns true.
I've investigated further and Users.methods.isAuthed() returns false after passing a 404 url matching this guide: https://reacttraining.com/react-router/web/example/no-match
/src/components/PrivatePage.tsx
// almost identical to ./PrivateRoute.tsx
import * as React from 'react'
import { Redirect, Route } from 'react-router-dom'
import User from '../data/users'
import Page from './Page'
const PrivatePage = ({ component: Component, ...rest }) => (
<Route {...rest} render={props => (
User.methods.isAuthed() ? ( **// after page 'notFound' this returns false**
<Page>
<Component {...props}/>
</Page>
) : (
<Redirect to={{
pathname: '/redirect',
state: { from: props.location }
}}/>
)
)}/>
)
export default PrivatePage
/src/data/users.tsx
import asteroid from '../asteroid/'
interface IUsers {
methods: {
isAuthed: () => boolean
}
}
const Users: IUsers = {
methods: {
isAuthed() {
const result = !!asteroid.userId
console.log({isAuthed: result})
return result
}
}
}
export default Users
/routes/App.tsx
import * as React from 'react'
import { Route, Switch } from 'react-router-dom'
import PrivatePage from '../components/PrivatePage'
import Login from './accounts/auth/'
import About from './root/About'
import Home from './root/Home'
import NotAuthorized from './root/NotAuthorized'
import NotFound from './root/NotFound'
interface IProps {}
interface IState {
isAuthorized: boolean
}
class App extends React.Component<IProps, IState> {
render() {
const Content = () => (
<div id='app-content'>
<Switch>
<Route path='/login' component={Login}/>
<Route path='/redirect' component={NotAuthorized}/>
<PrivatePage
path='/'
component={Home}
exact={true}
/>
<PrivatePage
path='/about'
component={About}
/>
<PrivatePage component={NotFound}/>
</Switch>
</div>
)
return (
<div id='app-container'>
<Content />
</div>
)
}
}
export default App

Categories

Resources