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")
}
Related
I have a basic component that goes out and gets user info via axios and then sets the users state. But in the component, I have another nested component that is a form type component that sets placeholders, defaultValue, etc.
This is the lifecyle method that gets the data and sets state:
componentDidMount() {
axios.get('https://niftyURLforGettingData')
.then(response => {
console.log(response.data);
const users = response.data;
this.setState({ users });
})
.catch(error => {
console.log(error);
});
}
Nested within this component is my form component:
<FormInputs
ncols={["col-md-5", "col-md-3", "col-md-4"]}
properties={[
{
defaultValue: "I NEED VALUE HERE: this.state.users.id",
}
/>
if I use just:
{this.state.users.id} outside the component it works... but inside form...nothing.
I am quite sure I have to pass the state into this component... but can't quite get it.
I am pretty sure it doesn't work because users is undefined when your component renders for the first time.
Try to initialize that variable in the state doing something like this:
state = {
users: {}
}
and then use a fallback since id will also be undefined doing this:
<FormInputs
ncols={["col-md-5", "col-md-3", "col-md-4"]}
properties={[
{
defaultValue: this.state.users.id || "Fallback Value", // this will render "Fallback value" if users.id is undefined
}
/>
If this is not the case please share more information about your situation.
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.
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.
I am passing the date picked to the this.state.value Getting a time stamp of Midnight that day, but I can not seem to get it to render a new page so I can build the booking page. Where it takes the time stamp and checks for available times that day. When I did return the Handlesubmit on a half successful rerender I got a white page and back to the main app page with a blank date to choose again.
I have tried to build this as a functional component in the handleSubmit But also tried to return a component from the handleSubmit.
Here is the last failed to compile attempt and the last successful compile
handleSubmit(event) {
render(
{
const {bookingTime} = this.state.value;
if (bookingTime) {
return <Redirect to='/Bookingpage' />;
}
}
event.preventDefault();
}
This fail was from doing something similar to https://github.com/salsita/redux-form-actions/issues/2#issuecomment-318774722
While this is the half successful runs code (Just a White Blank page for about 1s)
handleSubmit(event) {
return <h1>{this.state.value}</h1>;
event.preventDefault();
}
This is the last Successful run on StackBlitz
Check the components folder for and tool bar for files directly related to problem
https://react-dnudvg.stackblitz.io/
Please note the code is there but its not building the app.
I expected for this run render a new page with one <h1>{this.state.value}</h1> as defined by the date picker
It looks like the issue here is mixing up logic that should be rendered with logic that can go in an event handler method. In your first example, render is not a valid method that can be called from within handleSubmit, and in your second example, handleSubmit should not return components to be rendered.
You can get at your desired functionality by creating a state variable that indicates whether the user has submitted yet. Then you can check that variable when rendering (note that rendering is the return part of a functional component or the render method of a stateful component.
For example:
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
value: null, // for the date picker
wasSubmitted: false,
}
}
handleSubmit = event => {
event.preventDefault();
this.setState({wasSubmitted: true});
}
render() {
const { value, wasSubmitted } = this.state;
if (wasSubmitted) {
return <Redirect to='/Bookingpage' />
} else {
return //... your normal component
}
}
}
In my stores, I have functions set to handle errors in API calls.
One example is for authorization - if a user cannot view a project, they get an unauthorized error.
In my store, I set an error property on the state.
Then, in my component's render function, I first check for this.state.error. This works fine - I render a reused error component containing the error message and code.
The problem is that I need to reset the error state to null after the user moves on - yet resetting the state causes the component to re-render.
My current approach (in my component's render function):
if (this.state.error) {
return (
<Error
errorTitle={this.state.errorCode}
errorMessage={this.state.error}
clearErrors={this.clearErrors}
/>
)
}
And a function that belongs to the same class:
clearErrors: function () {
this.setState({
error: null,
errorCode: null
});
},
And then my Error component:
var Error = React.createClass({
clearErrors: function () {
this.props.clearErrors();
},
render: function () {
return (
<Panel className='errorPanel' header={this.props.errorTitle} bsStyle='danger'>
<p>{this.props.errorMessage}</p>
<a href='#/dashboard'>
<Button onClick={this.clearErrors}>Return to Dashboard</Button>
</a>
</Panel>
)
}
});
The problem is evident - before the onClick actually returns my user to the dashboard, it very quickly renders the component that the user is not supposed to be able to see.
How should I be handling this?
The errorcode should not in the components state. Because (apparently) it does not correspond fully with the components state. From what I gather you are looking for is the following states:
Error code = not null: error component displayed
Error code reset to null: error component still displayed (because you do NOT want to re-render the component)
So I would suggest removing error from state and do something like:
render() {
errorcode = this.props.errorcode;
if (this.props.errorcode) {
return <ErrorComponent...>
}
clearErrors() {
errorcode = null;
}