I'm working on an app that has user authentication. Right now my log in is currently happening in a withAuth HOC, while my Nav component is set up to get the current user.
However, while the use can log in and access their profile page, once they leave profile, current user is empty.
How do I set up my store and/or components so that current user is available on every page.
I tried to pass user to my Nav component, but once you leave profile, there's no user at all
This is my withAuth HOC:
export default function withAuth(WrappedComponent) {
class Something extends React.Component {
componentDidMount() {
**//checks if user is logged in before loading to the WrappedComponent (which in this case is the profile page)**
if (!localStorage.token) {
this.props.history.push("/login")
}
try {
this.props.getCurrentUser()
.catch(error => {
this.props.history.push("/login")
})
} catch (error) {
if (error.message === "Please log in") {
this.props.history.push("/login")
}
}
}
render() {
return (
<WrappedComponent />
)}
}
const mapDispatchToProps = {
getCurrentUser: getCurrentUserAction
**//does a fetch to 'http://localhost:3000/profile' with the bearer token and returns the username**
}
return withRouter(connect(null, mapDispatchToProps)(Something))
}
This is my Nav component (which is imported to every page):
componentDidMount() {
this.props.getCurrentUser();
**//This should fetch from 'http://localhost:3000/profile' with the bearer token and return the username**
}
...
const mapStateToProps = state => {
return {
user: state.user
};
};
const mapDispatchToProps = {
getCurrentUser: getCurrentUserAction,
logout: userLogoutAction
};
I understand why user is visible in profile, but don't understand why it's not visible anywhere else
When HoC component mounts it always mounts the Nav component as well.
Nav component has this.props.getCurrentUser(); which is same thing as this.props.setCurrentUser().
I bet you have some race condition that redirects the user back to login.
I'd suggest you to refactor your code and use redux properly, which in this scenario could be:
getCurrentUserAction handles the request to get user data if user is logged in, then dispatches RECEIVE_USER action, which changes the redux state (by reducer).
HoC makes sure user is logged in and dispatches the action when necessary.
Nav component uses only selector to get the user data it needs (since HoC acts here as a guard).
Above might not be the most optimal solution for you, but it could fix your problem.
Related
Im using auth0, and on when the app gets re rendered maybe on a page refresh, im trying to do a couple of things with auth0, 1st is check if the user is authenticated, and if he is save his token and user object in the mobx store else redirect him to the login page.
this is the code im currently using:
// appLoaded --> to check if the user is logged in (app cannot be used without a user)
const App: React.FC = () => {
const {
// isAuthenticated,
user,
getIdTokenClaims,
loginWithRedirect,
isAuthenticated
} = useAuth0();
const {
sideNavStore: { isOpen, toggleSideBar },
authStore: { setAppLoaded, setToken, setUser, appLoaded }
} = useStore();
// get the user from auth0 and store their details in the mobx store;
useEffect(() => {
if (isAuthenticated) {
getIdTokenClaims().then((response) => {
// eslint-disable-next-line no-underscore-dangle
setToken(response.__raw);
setUser(user);
});
} else {
loginWithRedirect().then(() => {
setAppLoaded(false);
});
}
}, [
isAuthenticated,
setToken,
setUser,
setAppLoaded,
loginWithRedirect,
getIdTokenClaims,
user
]);
if (!appLoaded) return <PreLoader />;
return (
// some component code
)}
i run into an infinite loop in the code above. i have been on this issue for a day now and just decided to ask on here. could anyone please tell me what im doing wrong? thanks
You shouldn't update any state that a useEffect depends on from within it. That creates an infinite loop.
You have too many dependencies for single useEffect and you're updating some of them. Split your code into multiple useEffect with smaller number of dependencies, ensuring that each of them aren't updating it's own dependency
I have lots of static forms which i show the user when he clicks on the main menu and goes to a specific route, what i want to do now is to check when going to a route if that component has permission to be visited, i can do this by doing a simple post to server but i am confused and i don't know where should be the place to do this check or post.
Here are some of the solutions i thought of:
1- Writing a Higher order component and wrapping each static component with it
2- creating a base class and making each static form to inherit it while doing this check in the parent class
3- Or maybe using the routes as a solution since i am using the react-router ?
I will appreciate any help or tips.
Thanks.
Create a custom hook like so:-
const useAdmin = (url:string) => {
const [admin, setAdmin] = React.useState(false);
React.useEffect(() => {
post(url, {some: body}).then(res => {
setAdmin(res.admin);
}).catch(err => {
setAdmin(false);
});
}, [])
return [admin];
}
Then use it anywhere:-
const mycomponent = props => {
const [admin] = useAdmin('https://foobar.com/checkPermission');
//Rest of the logic according to the variable 'admin'
return (
<div>
{
admin? <div/>:null
}
</div>
)
}
Or think of admin as permission. Pass it some url for different routes and it will handle it.
I do something similar using react-router as well. I made my own route component that wraps around react-router's route that checks permissions and conditionally renders the route or redirects.
If you're doing the api call each time, then it would look something close to this.
class AppRoute extends Component {
state = {
validCredentials: null
}
componentDidMount() {
// api call here + response
if (ok) {
this.setState({validCredentials: true})
} else {
this.setState({ validCredentials: false})
}
}
render() {
const { validCredentials } = this.state
if (validCredentials) {
return <Route {...this.props} />
} else if (validCredentials === false) {
return <Redirect to="somewhere"/>
}
return null
}
}
You can definitely accomplish this using a Higher Order Component. Just set a state for the user on login like "admin" or "basic_user." According to this state some buttons or forms are going to be available for the user to access. You can also save these access permissions to your backend and call it in the HOC whenever the user logs in.
Before I load my React App I need to check 2 conditions
User is Logged in, if not redirect to login page
All of the
User Settings fetched using API, if not display a loading screen.
So, inside render method, I have below conditions:
if (!this.isUserLoggedIn()) return <NotifyPleaseLogin />;
else if (!this.state.PageCheck) {
return (
<PageLoading
clientId={Config.clientId}
setPageReady={this.setPageReady()}
/>
);
} else {
return "Display the page";
In this scenario, what I expect to see happen is that, if user is not logged in, user redirected to login page. If user logged in and currently page is fetching the API query, user will see the PageLoading component (loading screen) and lastly if page is ready, the page will get displayed.
Right now I am getting Cannot update during an existing state transition (such as within render). Render methods should be a pure function of props and state. error, which is because I am doing a setState update within Render method of the parent and also I am getting TypeError: props.setPageReady is not a function at PageLoading.js:29 error when I try to run parent's function that sets the state of PageReady to true like below
setPageReady() {
this.setState({ PageCheck: true });
}
How can I set this up so child can display a loading page until the page is ready (During this child can do an API call and retrieve user settings) then let parent know all settings are retrieved and are in the redux so parent can proceed loading the page?
You can easily achieve this by adding more states to actively control your component:
state = {
isAuthorized: false,
pagecheck: false
};
We move the authorization check to a lifecylcle-method so it doesn't get called every render.
componentDidMount() {
if(this.isUserLoggedIn()) {
this.setState({
isAuthorized: true
});
}
}
Using our state, we decide what to render.
render() {
const {
pagecheck,
isAuthorized
} = this.state;
if(!isAuthorized){
return <NotifyPleaseLogin />;
}
if(!pagecheck) {
return (
<PageLoading
clientId={Config.clientId}
setPageReady={() => this.setPageReady()}
/>
);
}
return "Display the page";
}
Note: Previously you passed this.setPageReady() to Pageloading. This however executes the function and passes the result to Pageloading. If you want to pass the function you either need to remove the braces this.setPageReady or wrap it into another function () => this.setPageReady()
You can pass PageCheck as prop from Parent to and show/hide loader in component based on that prop.
<PageLoading
clientId={Config.clientId}
pageCheck={this.state.PageCheck}
setPageReady={this.setPageReady}
/>
Then call setPageReady inside the success and error of the API call that you make in the child function:
axios.get(api)
.then((response) => {
//assign or do required stuff for success
this.props.setPageReady();
})
.catch((error) => {
//do error related stuff
this.props.setPageReady(); //as you need to hide loader for error condition as well
})
state = {
isAuthorized: false,
pageCheck: false
};
componentDidMount() {
if(this.isUserLoggedIn()) {
this.setState({
isAuthorized: true
});
}
}
{!this.state.isAuthorized ?
<NotifyPleaseLogin />
:
(!this.state.pageCheck ?
<PageLoading
clientId={Config.clientId}
setPageReady={this.setPageReady()}
/>
:
"Display the page")
}
Is there anything wrong with this flow in redux? I change isAuthenticated in my reducer like so
export function userReducer(state=initState, action) {
switch(action.type){
case AUTH_SUCCESS:
return {
...state,
isAuthenticated: true
}
//...
//...
}
}
so in my render I check isAuthenticated if equal to true I redirect the user to login.
#connect(state => state.user, {loginUser})
class LoginForm extends React.Component {
constructor() {
super()
this.state = {
email: '',
password: ''
}
}
handleSubmit() {
const { email, password } = this.state
this.props.login(email, password)
}
render(){
const { email, password } = this.state
return(
<div>
{this.props.user.isAuthenticated && <Redirect to={'/dashboard'} />}
<Form input={{email, password}} handleSubmit={this.handleSubmit} />
</div>
)
}
}
export default LoginForm
Another way is I can make the render method cleaner, I use componentWillRecieiveProps
componentWillReceiveProps(nextProps) {
if(nextProps.user.isAuthenticated !== this.props.user.isAuthenticated && nextProps.user.isAuthenticated) {
this.props.history.replace('/dashboard')
}
}
Which one is appropriate and why?
They are both valid ways, except componentWillReceiveProps is deprecated in react v16.3 so it would be better to use componentDidUpdate. And I would simplify logic as follows:
componentDidUpdate(prevProps) {
if(prevProps.user.isAuthenticated !== this.props.user.isAuthenticated) {
this.props.history.replace('/dashboard')
}
}
There is one difference though, that with Redirect you don't have to create class component but you can use it in stateless component and it doesn't even have to be inside Route or connected withRouter.
const SomeComponent = ({ isLogged }) => (
<div>
{isLogged <Redirect to="/path" />}
<div>something</div>
</div>
)
Both of your methods are equivalent since, Redirect also replaces the current history stack and so does this.props.history.replace(). You can use anyone of the methods.
Also considering that React wants to deprecate the componentWillReceiveProps from v17 onwards you are better of using componentDidUpdate for the same
However componentDidUpdate here has a slight disadvantage here that for a fraction of a second Form will be visible and then redirect will happen which might be a bad user experience.
So consider future development in React, you might use the Redirect approach instead of doing prop comparison and using history.replace()
As others have said, they're both valid.
Just from a react point of view, a login component should handle exactly that - logging in.
I would expect a higher component than the login page (the app perhaps), to do the logic of whether to display the Login form, or to redirect. E.g. something like this:
import {withRouter} from 'react-router-dom';
class App {
render() {
if (this.props.user.isAuthenticated && this.props.location === '/login') {
return <Redirect to={'/dashboard'} />;
}
return (<div>
REST OF APP HERE
</div>);
}
}
export default withRouter(props => <App {...props}/>);
The benefit here is you can easily test your login form, as well as compose it within other components really easily. Your routing concerns are effectively global, and those can live up at the top with your app.
I'm trying to use the new React context to hold data about the logged-in user.
To do that, I create a context in a file called LoggedUserContext.js:
import React from 'react';
export const LoggedUserContext = React.createContext(
);
And sure enough, now I can get access to said context in other components using consumers, as I do here for example:
<LoggedUserContext.Consumer>
{user => (
(LoggedUserContext.name) ? LoggedUserContext.name : 'Choose a user or create one';
)}
</LoggedUserContext.Consumer>
But obviously, for this system to be useful I need to modify my context after login, so it can hold the user's data. I'm making a call to a REST API using axios, and I need to assign the retrieved data to my context:
axios.get(`${SERVER_URL}/users/${this.state.id}`).then(response => { /*What should I do here?*/});
I see no way to do that in React's documentation, but they even mention that holding info of a logged in user is one of the use cases they had in mind for contexts:
Context is designed to share data that can be considered “global” for
a tree of React components, such as the current authenticated user,
theme, or preferred language. For example, in the code below we
manually thread through a “theme” prop in order to style the Button
component:
So how can I do it?
In order to use Context, you need a Provider which takes a value, and that value could come from the state of the component and be updated
for instance
class App extends React.Component {
state = {
isAuth: false;
}
componentDidMount() {
APIcall().then((res) => { this.setState({isAuth: res}) // update isAuth })
}
render() {
<LoggedUserContext.Provider value={this.state.isAuth}>
<Child />
</LoggedUserContext.Provider>
}
}
The section about dynamic context explains it
Wrap your consuming component in a provider component:
import React from 'react';
const SERVER_URL = 'http://some_url.com';
const LoggedUserContext = React.createContext();
class App extends React.Component {
state = {
user: null,
id: 123
}
componentDidMount() {
axios.get(`${SERVER_URL}/users/${this.state.id}`).then(response => {
const user = response.data.user; // I can only guess here
this.setState({user});
});
}
render() {
return (
<LoggedUserContext.Provider value={this.state.user}>
<LoggedUserContext.Consumer>
{user => (
(user.name) ? user.name : 'Choose a user or create one';
)}
</LoggedUserContext.Consumer>
</LoggedUserContext.Provider>
);
}
}
I gave a complete example to make it even clearer (untested). See the docs for an example with better component composition.