I am currently trying to implement some basic authentication by wrapping a react Route element.
Here loggedIn is the hook value, and setLoggedIn is the "setter". The setter is passed by props to the "Login" component which correctly sets the hook's value. However, the "App" return function renders with the old value.
Functional Component:
function App() {
const [loggedIn, setLoggedIn] = useState(false);
return (
<Router>
<div className="App">
<Route path="/budget" component= {BudgetDisplay}/>
<Route path="/login" render={() => <Login loggedIn={loggedIn} setLoggedIn={setLoggedIn}/>}/>
<PrivateRoute path="/admin" loggedIn={loggedIn} component={Admin}/>
</div>
</Router>
);
}
export default App;
Route Component wrapper:
const PrivateRoute = ({ component: Component, loggedIn, ...rest }) => (
<Route {...rest} render={(props) => (
loggedIn === true
? <Component {...props}/>
: <Redirect to="/login"/>
)}/>
)
As the return utilises the previous value, the redirect component is rendered instead of the correct Component.
I am aware of the useEffect method, however I am not sure how to implement it such that "App" returns the correct value.
I have tried making the same Functional Component as a Class Component using setState instead, but ran into the same issue.
Edit:
const PrivateRoute = ({ component: Component, loggedIn, ...rest }) => {
console.log(loggedIn)
if ( loggedIn ) {
return <Route {...rest} render={(props) => <Component {...props}/>}/>;
} else {
return <Route {...rest} render={() => <Redirect to="/login"/>}/>;
};
};
The correct value is logged here, loggedIn = true, however the Redirect is returned.
The solution was removing the {...rest} from PrivateRoute, still don't know why though.
const PrivateRoute = ({ component: Component, loggedIn, ...rest }) => {
if ( loggedIn ) {
return <Route {...rest} render={(props) => <Component {...props}/>}/>;
} else {
return <Route render={() => <Redirect to="/login"/>}/>;
};
};
Related
I'm loading a react app that has a root component App.js which is determining if an authenticated user exists through a central redux store, but the problem is it takes a fraction of a second to resolve. Until then the user is getting a flash of the login page even if the user is logged in, which I'm sure counts as bad user experience. Is there a way to not show anything at all until the the status is resolved. I'm attaching a code snippet.
function App(props) {
const cookies = new Cookies();
const { user } = props;
if (cookies.get("authToken") === "null" || cookies.get("authToken") === undefined) {
//console.log("no valid token");
} else {
if (user === null) {
props.fetchLoggedInUser(cookies.get("authToken"));
}
}
const isLoggedIn = user ? (
<div className="App s12 l12 m12">
<Navbar user={user}/>
<Switch>
<Route exact path="/" component={() => (<Home user={user} />)}></Route>
<Route exact path="/create_blog" component={() => (<CreateBlog user={user} />)}></Route>
<Route exact path="/edit_profile" component={() => (<EditProfile user={user} />)}></Route>
</Switch>
</div>
) : (
<div className="App">
<Navbar user={null}/>
<Switch>
<Route exact path="/" component={() => (<Home user={null} />)}></Route>
<Route exact path="/log_in" component={LogIn}></Route>
<Route exact path="/sign_up" component={SignUp}></Route>
</Switch>
</div>
);
return (
<BrowserRouter>
{isLoggedIn}
</BrowserRouter>
);
}
const mapStateToProps = (state) => {
return {
authError: state.auth.authError,
token: state.auth.token,
user: state.auth.user
}
}
const mapDispatchToProps = (dispatch) => {
return {
fetchLoggedInUser: (token) => dispatch(fetchLoggedInUser(token))
}
}
export default connect(mapStateToProps, mapDispatchToProps)(App);
Create a state variable isLoading (or with ay name you want). set its initial value to true.
if (isLoading) {
return '';
} else {
return (
<BrowserRouter>
{isLoggedIn}
</BrowserRouter>
);
}
Once you get the value of isLoggedIn you can set back isLoading to false and react will rerender.
I have a react + firebase application which has protected routes. I face issue if a logged in user accesses the login page. The issue is that the login page gets displayed for a second and then redirects to the home page i.e protected route. I feel the issue is because the value retrieved from context in the login page to check if user is authenticated gets updated after the route is resolved. Can someone give me pointers on how should I fix this. Ideally I would not want the user to see the login page for sometime if the user is already authenticated.
//App.js
render() {
return (
<AuthProvider>
<Router>
<Switch>
<PrivateRoute exact path="/" component={Home}></PrivateRoute>
<Route exact path="/login" component={LoginPage}></Route>
<Route exact path="/signup" component={SignUp}></Route>
</Switch>
</Router>
</AuthProvider>
);
}
}
//AuthProvider
import React, { useEffect, useState } from "react"
import { fire } from "./Fire"
export const AuthContext = React.createContext();
//this component will maintain the current user throughout the app
export const AuthProvider = ({ children }) => {
const [currentUser, setCurrentUser] = useState(null)
//empty array as second arg to useEffect hook as we only want to trigger it once
useEffect(() => {
console.log("useEffect")
fire.auth().onAuthStateChanged(setCurrentUser)
}, [])
return (
<AuthContext.Provider value={{ currentUser }}>{children}</AuthContext.Provider>
)
}
//PrivateRoute
const PrivateRoute = ({ component: RouteComponent, ...rest }) => {
//useContext hook makes it very easy to retrieve the value
const { currentUser } = useContext(AuthContext)
return (
<Route {...rest} render={
routeProps => {
console.log("currentUser" + currentUser);
return !!currentUser ? (
<RouteComponent {...routeProps} />
) : (
<Redirect to={"/login"} />
)
}
} />
)
}
//login
render() {
if (this.props.context.currentUser)
return <Redirect to="/" />
return (
<Login email={this.state.email} password={this.state.password} inputHandler={this.onInputChange} loginHandler={this.onLoginClick} />
)
}
You should probably add a loading check in your PrivateRoute file which will just show a Loading... or a loader if the user is not loaded yet.
For that you will have to do some minor changes in your AuthProvider file and in PrivateRoute.
//PrivateRoute
const PrivateRoute = ({ component: RouteComponent, ...rest }) => {
//useContext hook makes it very easy to retrieve the value
const { currentUser } = useContext(AuthContext)
return (
<Route {...rest} render={
routeProps => {
console.log("currentUser" + currentUser);
return !!currentUser ? (
<RouteComponent {...routeProps} />
) : currentUser === 'loading' ? <h1>Loading...</h1>
:(
<Redirect to={"/login"} />
)
}
} />
)
}
This is my main app.js file --- I'm trying to pass down the state values and functions defined here as props to my child components through react-router-dom:
import React, { Component } from 'react'
class BooksApp extends Component {
state = {
bookRepository: [{tuna: 'sandwhich'}],
searchResults: [],
featuredBook: {}
}
fixer = (someBooks, type) => { //code }
async updateBookRepository(bookID, shelfID) { //code }
async updateSearchResults(userQuery) { // code }
async updateFeaturedBook(bookID, shelfID) {//code }
render() {
const myProps = {
bookRepository:this.state.bookRepository,
searchResults:this.state.searchResults,
featuredBook:this.state.featuredBook,
updateBookRepository:this.updateBookRepository,
updateSearchResults:this.updateSearchResults,
updateFeaturedBook:this.updateFeaturedBook
}
return (
<div>
<Route exact path='/' render={(props) => (
<Bookshelves {...props} {...myProps} />
)}/>
<Route path='/search' render={(props) => (
<Searchpage {...props} {...myProps}/>
)}/>
<Route path='/featuredBook/:bookID' render={(props) => (
<Featuredbook {...props} {...myProps}/>
)}/>
</div>
)
}
}
I'm accessing the props like so:
class Bookshelves extends Component {
state = {
shelves: ['currentlyReading', 'wantToRead', 'read']
}
render() {
const { bookRepository } = this.props;
return (
<div>
The props are: {console.log(this.props)}
</div>
)
}
}
And this works, and they show all show up under my props when I attempt to access them, but I'm having trouble udnerstanding WHY I need to define my own object and then pass that down to my child components.
Is there some way that I can assign them to the props-ish object itself so that...
<Route exact path='/' render={(props) => (
<Bookshelves {...props} />
}/>
...would pass them down?
You can destructure everything in one line
<Route exact path='/' render={(props) => (
<Bookshelves {...props, ...this.state} />
)}/>
So I am running into the infamous React Router v4 warning:
Warning: You should not use <Route component> and <Route children> in the same route; <Route children> will be ignored
This happens after wrapping my component, which itself renders a <Route /> inside my HOC which provides my React context.
This all looks a bit like this:
App.js:
<myContextProvider>
<Router>
<Switch>
<myLayout path="/foo" Component={someComponent}>
...
</Switch>
</Router>
</myContextProvider>
my stores.js file includes the following:
export const withMainStore = (Component) => {
return (props) => {
return (
<MainConsumer>
{store => (
<Component {...store} {...props} />
)}
</MainConsumer>
)
}
}
myLayout.js:
class myLayout extends React.Component {
const { Component, ...rest } = this.props;
// ...
render() {
return (
<Route {...rest} render={(props) => (
<Component {...rest} {...props} />
)}/>
)
}
}
export default withMainStore(myLayout)
I hope my whole situation here is somewhat clear. can someone explain to me why the warning occurs? At NO point in my whole app, I have something like: <Route>...</Route> - I am always using <Route {...rest} render={(props) => ( ... )} />!
So I got no idea why this is happening. I has to have something todo with my withMainStore HOC, because when I remove it the error is gone. but still I do not understand why this causes the error.
This is based almost entirely on the React-router basic auth example. The only difference is that the user data comes from redux with mapStateToProps. So why is the initial this.props.isLoggedIn simply empty?
const PrivateRoute = ({ component: Component, userLoggedIn, ...rest }) => (
<Route {...rest} render={props => (
userLoggedIn ? (
<Component {...props}/>
) : (
<Redirect to={{
pathname: '/login',
state: { from: props.location }
}}/>
)
)}/>
)
class App extends Component {
componentWillMount () {
console.log(this.props.isLoggedIn) // getting correct value
}
render () {
return (
<Router>
<div>
{this.props.isLoggedIn} // <============= EMPTY :(
<Route exact path="/" render={() => (
!this.props.isLoggedIn ? (
<Redirect to="/login"/>
) : (
<Redirect to="/studio"/>
)
)}/>
<Route path="/login" component={Login}/>
<PrivateRoute
path="/studio"
component={Studio}
userLoggedIn={this.props.isLoggedIn}
/>
</div>
</Router>
)
}
}
const mapStateToProps = state => ({
isLoggedIn: state.user.isLoggedIn,
})