The props of a component rendered inside a route will never update - javascript

What I am trying to do is getting the login user's information from the login component, so that the App component could pass the login user's information to the main component.
Here is some code in the App.js:
state = {
usersArr: [], // all the users
logInUser: 0, // the login user, default value as 0
}
getLogInUser = () => {
return this.state.logInUser;
}
assignLogInUser = user =>{
this.setState({logInUser: user});
console.log("LogIn: " + this.state.logInUser.username);
}
render(){
return(
<div className="App">
<Router>
<div className="App">
<Switch>
<Route path="/main" exact render={() => <Main logInUser={() => this.getLogInUser()}/>}/>
<Route path="" exact render={() => <Landing usersArr={this.state.usersArr} getUser = {this.assignLogInUser}/>}></Route>
</Switch>
</div>
</Router>
)
}
The first page would be landing page and the landing component would be rendered. The landing component will render the login component. The login component will check if the username and password from input would match any user in the usersArr. If it does match, the login component will call the assignLogInUser to assign the login user.
landing.jsx:
render(){
return(
<Login usersArr={this.props.usersArr} getUser={this.props.getUser}/>
)
}
login.jsx:
constructor(props){
super(props);
this.userName = React.createRef(); // username referrence
this.password = React.createRef(); // password referrence
}
validateLogin = () => {
this.props.usersArr.forEach(user => {
if(user.username === this.userName.current.value && user.password === this.password.current.value){
this.props.getUser(user);
}
})
}
The problem is that the logInUser passed into the main is always 0.
Looks like once the route has been set up, it will never update the component it's going to render. No matter what I tried, I would get the default value 0 for logInUser in the main component.
I can probably use redux, but I have to refactor all of the code.
I have tried forceUpdate() in the main component, or using key in the main component. None of them works.
Any help would be appreciated.

Related

React Router and keycloak Route Guarding is redirecting upon page refresh

I am using React Router v5, keycloak-js and #react-keycloak/web to implement route guarding and user authentication. The main idea is that the SecuredPage component is to be protected from unauthenticated users, wherein they should be redirected to /login if they are not yet logged in.
Here is how my app is set up:
App.js:
const App = (props) => {
const keycloak = new Keycloak("/keycloak.json");
return (
<div className="app">
<ReactKeycloakProvider authClient={keycloak}>
<Switch>
<Route path="/login" exact>
<Login />
</Route>
<RouteGuard path="/secured" exact component={SecuredPage} />
</Switch>
</ReactKeycloakProvider>
</div>
);
};
And the following is my RouteGuard component:
const RouteGuard = ({ component: Component, ...rest }) => {
const { keycloak } = useKeycloak();
const isLoggedIn = keycloak.authenticated;
return (
<Route
{...rest}
render={(props) => {
if (isLoggedIn) {
console.log("USER IS LOGGED IN, RENDERING COMPONENT");
return <Component />;
} else {
console.log("USER NOT LOGGED IN, REDIRECT TO LOGIN");
return (
<Redirect
to={{
pathname: "/login",
state: {
error: "You must login to continue.",
from: props.location.pathname,
redirected: true,
},
}}
/>
);
}
}}
/>
);
};
The app works great if I manually navigate to /secured, then I get redirected to keycloak's login page. The problem is, once I am already logged in and I go to /secured page, and from within that page, reload (F5), I get thrown into /login even though my keycloak session is still up. This doesn't happen if I navigate normally or when using the back and forward buttons of the browser. It only seems to happen on page reload or when I manually type into the URL bar.
I'm thinking maybe the RouteGuard component returns the Redirect before the isLoggedIn variable gets initialized during a page reload, but I can't figure out how I should make it so that the RouteGuard component's redirect waits to check the isLoggedIn variable?
Is there a better way on implementing Route Guards using React Router?

How to access to Home component even though not logged in?

In a React project, I have created certain components which have access only when logged in or else would be redirected to Login Page. While I was told to make few changes that are, the user should have access to the Home page even though not logged in. For accessing other components, the user must have a token. See the following code for reference
export const UserContext = createContext();
const Routing = () => {
const history = useHistory()
const { state, dispatch } = useContext(UserContext)
const [value, setValue] = useState(null)
const user = sessionStorage.getItem('token')
useEffect(() => {
if(user) {
dispatch({ type: "USER", payload: user })
} else {
history.push('/login')
}}, [])
return (
<>
<Router>
<Switch>
{/* Give access to user even though not logged in or has token */}
<Route exact path="/" component="Home" />
{/* I won't let user access this Component, unless token is available */}
<Route exact path="/videoCall" component="VideoCall" />
</Switch>
</Router>
</>
)
}
const App = () => {
const [state, dispatch] = useReducer(reducer, initialState)
return (
<UserContext.Provider value={{state, dispatch}}>
<Router>
<Switch>
<Route exact path="/login" component={LoginPage} />
<Routing />
</Switch>
</Router>
</UserContext.Provider>
)
}
export default App;
So, what could be the best possible solution, give access to the Home page even though not logged in, but, redirect the user to Login Page when trying to access other components like VideoCall.
Maybe something like this will help - a Router comonent of your own, which will check whether the user is authenticated and will decide whether to let him in the component or not. If you want the Home component to be rendered, just make it render, and components you don't want to be rendered if user is not logged in, just add a check in the component (or in the router component of you own) whether the user is authenticated...
BTW, every render, the user const is assigned the sessionstorage.getItem() value... every render. If you want it to happen only once, or only when a specfic variable changes - you should totally use useMemo (const user = useMemo(()=>sessionStorage.getItem(), [])) (https://reactjs.org/docs/hooks-reference.html#usememo)

How to set state before render with react(redux)

I want to make a component able to redirect when not loggedIn.
Components were made with react, checking auth function works well with redux.
//App.js
class App extends Component {
checkUserInfo () => {
const loggedInfo = storage.get('loggedInfo');
if(!loggedInfo) return;
const { UserActions } = this.props;
UserActions.setLoggedInfo(loggedInfo)
}
constructor(props) {
super(props);
this.checkUserInfo();
}
render() {
console.log(this.props.logged)
return(...)
}
}
export default connect((state) => ({logged: state.user.get('logged')}, (dispatch)=> ...)
and UserActions.setLoggedInfo action is look like this.
...
export default handleActions({
[SET_LOGGED_INFO]: (state, action) => {
return state.set('logged', true)
}
})
...
So, I want situation that component is redirected when auth is not logged in. I made a rendering component <Route/> with condition which is that if state.logged==false, <Redirect to='login/>.
But in very front point, logged is false before executing checkUserInfo function. so when I'm loggedIn, Redirect to /login, and when I'm not loggedIn, Redirect to /login too.
//PrivateRoute.js
...
render() {
const { logged } = this.props;
console.log(logged);
return(
<Route path="/myPage" render={props =>
logged ? <Component/> : <Redirect to={{pathname: '/login'}}/>
}/>
)
}
...
this is screenshot what is logged value in console.
I want to skip very front state before set state by myFunction(checkUserInfo), how can I do.
plz help me.
and sorry to not good english syntax.
You need to check your global state before rendering the private component.
render prop provided by Route is a good place for that
<Route path='/secretarea' render={() =>{
return props.isLoggedIn ? <SecretComp /> : <Login />
}}/>
Set PrivateRoute like this
This could help to check auth in simple way.

Passing callback to children, to update a different child

I am trying to create a Navbar (component), which changes slightly when a user logs in in a SignIn component. So, my app looks like this:
I have a state in App, with authenticated set to false as default. I then have a function, updatedAuthenticationEvent, which sets the value of the app state.
Ihave a NavBar component, which I pass in the value of Authenticated. The idea is that I change something on the navbar, if authenticated. That would be, dont show "Signin" and rather show "Sign Out". So when I sign in on my sign in component, the navbar changes.
I then have a few routes in my app, one of which is my Signin, which I attempt to pass my function to, as a callback.
export default class App extends React.Component {
constructor(props)
{
super(props);
this.state = {
authenticated: false
}
}
updatedAuthenticationEvent(isAthenticated)
{
console.log('Called!');
this.setState({authenticated: isAthenticated});
}
render() {
return (
<div>
<Router>
<div>
<Navbar authenticated={this.state.authenticated} />
<Route exact path='/' component={Home} />
<Route path="/login" component={Login} changeState={this.updatedAuthenticationEvent} />
</div>
</Router>
<Footer />
</div>
)
}
}
My SignIn component, actually wraps my sign in content.
But for now, I'm just trying to make something work.
So for now, all I am doing is that when the sign in component loads, set the Authenticated to true.
My goal though is to pass that prop to my LoginBox, which has all the logic, and my actual sign in method. But for now, I want this to work.
export default class SignIn extends Component {
constructor(props)
{
super(props);
}
componentWillMount()
{
this.props.changeState(true);
}
render() {
return (
<div className="row justify-content-md-center">
<div className="col-lg-4">
<LoginBox changeState={this.props.changeState} />
</div>
</div>
)
}
}
I get the error:
TypeError: this.props.changeState is not a function
What do I need to change to achieve this? Signin box event, should effect the Navbar.
well, I will not recommend this, but here it is,
use the render prop from Router, so you can pass the callback for changeState directly
<Route path="/login" render={(props) => <Login {...props} changeState={this.updatedAuthenticationEvent} />
then I would recommend change componentWillMount for componentDidMount in your Login Component
and finally
Use an arrow function for your updatedAuthenticationEvent callback like
updatedAuthenticationEvent = (isAthenticated) => {
....
}
So you do not have context problems when executing changeState
working demo

React Router v4: Sending requests when navigation changes

I'm coding an authentication with react-router v4 and I'm using the PrivateRoute with render props, like the documentation: Redirects (Auth)
What I'm trying to do is: Whenever the user navigates to a route, I want to dispatch an action to make a request to the backend to verify if he's logged in.
Like this:
// App.js
class App extends Component {
checkAuth = () => {
const { dispatch, } = this.props;
// callback to dispatch
}
render() {
const props = this.props;
return (
<Router>
<div className="App">
<Switch>
<Route exact path="/" component={Login} />
<PrivateRoute
exact
path="/dashboard"
component={Dashboard}
checkIsLoggedIn={this.checkAuth}
/>
{/* ... other private routes here */}
</Switch>
</div>
</Router>
);
}
In PrivateRoute.js I'm listening the route to check if it changes, but when a route changes, this function is called too many times, and that's a problem to dispatch an action to make a request.
// PrivateRoute.js
const PrivateRoute = ({ component: Component, auth, checkIsLoggedIn, ...rest }) => (
<Route
{...rest}
render={props => {
props.history.listen((location, action) => {
if (checkIsLoggedIn) {
// Here I check if the route changed, but it render too many times to make a request
checkIsLoggedIn(); // here is the callback props
}
});
if (auth.login.isLoggedIn) {
return <Component {...props} />;
} else {
return <Redirect to={{ pathname: "/login", state: { from: props.location } }} />
}
}
}
/>
);
I need a help to figure it out a good way to call the backend whenever the route changes.
Creating a Higher Order Component (HOC) is a very clean way to do this. This way, you won't need to create a separate PrivateRoute component, and it would take only one line of change to convert any Component from public to protected, or vice versa.
Something like this should work:
import React from 'react';
import { Redirect } from "react-router-dom";
export function withAuth(WrappedComponent) {
return class extends React.Component {
constructor(props) {
super(props);
this.state = {
isUserLoggedIn: false,
isLoading: true
};
}
componentDidMount() {
// Check for authentication when the component is mounted
this.checkAuthentication();
}
checkAuthentication() {
// Put some logic here to check authentication
// You can make a server call if you wish,
// but it will be faster if you read the logged-in state
// from cookies or something.
// Making a server call before every protected component,
// will be very expensive, and will be a poor user experience.
this.setState({
isUserLoggedIn: true, // Set to true or false depending upon the result of your auth check logic
isLoading: false
});
}
render() {
// Optionally, you can add logic here to show a common loading animation,
// or anything really, while the component checks for auth status.
// You can also return null, if you don't want any special handling here.
if (this.state.isLoading) return (<LoadingAnimation />);
// This part will load your component if user is logged in,
// else it will redirect to the login route
if (this.state.isUserLoggedIn) {
return <WrappedComponent authData={this.state} {...this.props} />;
} else {
return <Redirect to={{ pathname: "/login", state: { from: props.location } }} />;
}
}
}
}
Once you have that component in place, all you need to do is use the HOC in any component that you wish to have protected. For example, in your case, the export line in your Dashboard file would be something like this:
/* Dashboard.js */
class Dashboard extends React.Component { ... }
export default withAuth(Dashboard);
and in your App, you can use a simple Route component:
<Route exact path='/dashboard' component={Dashboard} />
Your App does not need to care about which routes are protected, and which ones aren't. In fact, only the actual components need to know that they are protected.
Hope this helps. Cheers! :)

Categories