react lifting state up after login verified - reactjs

I'd like to create component - - so that only authorised users can access it. the idea is that after receiving response 200 from backend in my Login component (which is a child component) I would somehow use authed:true as props in parent component. I understand this instructions https://reactjs.org/docs/lifting-state-up.html and also used similar procedure on other components. However, in this case, I am getting the error:
TypeError: this.props.authAfter200 is not a function
at Login.handleSetAuthAfterVerified200 (AuthExample.js:325)
at AuthExample.js:350
at <anonymous>
The problem is I am not able to define function as a prop in my child component.
This is the function in my parent component that should get called as a prop.
setAuthToTrue=() => {
this.setState({authed:true})
}
but I never get to here.
This is how I have defined my Route router in AuthExample(parent) component - using react-router-with-props for that:
<PrivateRoute
exact
path="/private"
redirectTo="/login"
component={DailyNewsForUOnly}
text="This is a private route"
authAfter200={this.setAuthToTrue}
authed={this.state.authed}
/>
this is the part in my child component - Login.js where I should be able to define function on my props to pass to the parent component - but I just cannot:
handleSetAuthAfterVerified200() {
//this.setState({authed:true})
this.props.authAfter200()
}
verifyToken() {
let request = {
method: "post",
url: "https://xxxxxx/xxxx/verify/",
data: {
user: this.state.email,
token: this.state.token
},
withCredentials: true
};
console.log("request to verify");
console.log(request);
this.setState({ requestInMotion: true });
axios(request)
.then(response => {
console.log("response from verify", response);
if (response.status === 200) {
this.handleSetAuthAfterVerified200()
this.setStateAfterVerified200()
}
})
.catch(error => {
console.log(error);
this.setState({ requestInMotion: false, validationState: "error" });
console.log(
"this.state in the error part of response in verify",
this.state
);
});
}

i would suggest u to try to add debuggers in
react-router-with-props/PrivateRoute.js and in
react-router-with-props/renderMergedProps.js
and check if your props are actually being passed on properly.
he is doing very simple job of either allowing react to render your component or to redirect to url provided as prop.
I would suggest you rather use HigherOrderComponents or OnEnter route hook to handle these situations than a third party component.

Related

How do I re-render react components on url change?

I have a route like so:
<BrowserRouter>
<Switch>
<Route path="/question/:title" component={Item} />
</Switch>
</BrowserRouter>
The component uses axios to fetch data and update the content on the site, this is done in the componentWillMount function:
componentWillMount(){
const {title} = this.props.match.params;
axios.get(server + '/api/question/slug/' + title)
.then(response => {
this.setState({
question: response.data,
loading: false
})
})
}
The problem now is that, let's say I'm on a page "site.com/question/this-is-an-article",
and I use <Link to="/question/second-article">Second Article</Link> to navigate, it's like the componentWillMount function is not being called so the former content still remains on the page.
How can I make the componentWillMount function to run when there is a change in the url?
First of all, use safe life-cycle is preferred react-lifecycle-methods-diagram
Since you are using react-router-dom, you can get your URL via render props like this, refer to document here
history.location.pathname
So you may try to check if url changed in shouldComponentUpdate like this
shouldComponentUpdate(prevProps, prevState) {
return this.props.history.location.pathname !== prevProps.history.location.pathname
}
You can add more condition inside of it, or just prevent complex condition via component design adjustment
Try using the componentDidUpdate for the axios calling. Necessarily - you want to call the api when title(a prop) parameter change.
You may also try componentWillReceiveProps/UNSAFE_componentWillReceiveProps for the same.
P.S. - use componentDidMount along with above aptly. Look for lifecycle here http://projects.wojtekmaj.pl/react-lifecycle-methods-diagram/
I think you don't need any life cycle, (if yes, just use componentDidMount):
class App extends Component {
constructor() {
super();
this._initHistoryListen();
}
_unListen = () => {};
_initHistoryListen() {
this._unListen = history.listen((location, action) => {
const {
title
} = this.props.match.params;
axios.get(server + '/api/question/slug/' + title)
.then(response => {
this.setState({
question: response.data,
loading: false
})
})
});
}
componentWillUnmount(){
this._unListen()
}
}

React : How to make the SAME component re-render?

I've found myself at a bit of dead end here. I'll try to explain it as best as I can.
I'm using base username routing on my app, meaning that website.com/username will route to username's profile page. Everything works fine apart from one issue that I'll get to below.
This is how I'm doing it (very short version) :
This route looks for a potential username and renders the Distribution component.
<Route exact path="/([0-9a-z_]+)" component={Distribution} />
Distribution component then extracts the potential username from the pathname...
username = {
username: this.props.location.pathname.substring(1)
};
...And then fires that off to my API which checks to see if that username actually exists and belongs to a valid user. If it does it returns a user object, if not it returns an error.
if (username) {
axios
.post(API_URI + '/users/get/profile', JSON.stringify(username))
.then(res => {
this.setState({
ready: true,
data: res.data,
error: ''
});
})
.catch(err => {
this.setState({
ready: true,
data: '',
error: err.response
});
});
}
All of the above is happening inside componentWillMount.
I then pass the relevant state info as props to the relevant child component in the render :
render() {
if (this.state.ready) {
if (this.state.data) {
return <UserProfile profile={this.state.data} />;
}
if (this.state.error) {
return <Redirect to="notfound" />;
}
}
return null;
}
As I mentioned, this all works perfectly when moving between all of the other routes / components, but it fails when the Distribution component is called while already IN the Distribution component. For example, if you are already looking at a valid profile (which is at Distribution > UserProfile), and then try to view another profile, (or any other malformed username route that would throw an error), the API call isn't getting fired again so the state isn't being updated in the Distribution component.
I originally had it all set up with a Redux store but had the exact same problem. I wrongly thought that componentDidMount would be fired every single time the component is called for the first time, and I assumed that throwing a new url at it would cause that but it doesn't.
I've tried a bunch of different ways to make this work (componentWillReceiveProps etc) but I just can't figure it out. Everything I try throws depth errors.
Am I missing a magical piece of the puzzle here or just not seeing something really obvious?
Am I going about this entirely the wrong way?
You on the right path when you tried to use componentWillReceiveProps. I would do something like the following:
componentDidMount() {
this.refresh()
}
componentWillReceiveProps(prevProps) {
if(prevProps.location.pathname !== this.props.location.pathname) {
this.refresh(prevProps)
}
}
refresh = (props) => {
props = props || this.props
// get username from props
// ...
if (username) {
// fetch result from remote
}
}

react-redux-firebase login promise resolving before auth state is updated, causing render troubles

I am using redux-form, react-redux-firebase, along with react-router-redux and react-router.
When logging in a user with a redux-form connected form, in the onSubmit callback function, I am using the react-redux-firebase function from the withFirebase HOC called props.firebase.login(). Inside the return of this promise, I am attempting to use react-router to navigate to a "protected" view of mine.
This all works well and looks like this:
export default withFirebase(
connect(mapStateToProps, mapDispatchToProps)(
reduxForm({
form: SINGLE_PAGE_FORMS.login,
onSubmit: (values, dispatch, props) => {
const { firebase } = props;
const { username: email, password } = values;
firebase.login({
email,
password,
}).then(response => {
if(!!response) {
dispatch(
openNotificationCenterAC({ message: 'Successfully logged in!', messageType: MESSAGE_TYPE_SUCCESS })
);
// The problem here with login is that the Private Route has not yet seen the Uid update and therefore
// won't load a private route. I need to know the UID is in state before navigating to dashboard.
dispatch( push( ROUTE_OBJS.SECURED[ROUTES.DASHBOARD].path ) );
dispatch( reset(SINGLE_PAGE_FORMS.login) );
}
}).catch(error => {
dispatch(
openNotificationCenterAC({ message: error.message, messageType: MESSAGE_TYPE_ERROR })
);
});
},
})(LoginContainerView)
)
);
The trouble (as seen in comments) is that I have switched over (today) to using a really cool new way of making some of my routes Private. I used the examples found in this link here to create this concept.
My Private component looks like this:
export default class PrivateRouteView extends Component {
render() {
const {
key,
auth,
path,
renderRoute,
showErrorMessage,
} = this.props;
const { uid } = auth;
return (
<Route
exact
key={key}
path={path}
render={
(!!uid && isLoaded(auth) && !isEmpty(auth)) ? renderRoute :
props => {
return (
<Redirect
to={{
pathname: '/login',
state: { from: props.location }
}} />
);
}
} />
);
}
}
The auth prop here is from the withFirebase HOC injection. It's attached to this.props.firebase.auth.
The Problem
Whenever I successfully log in, get the user data returned to me in the .then function of the firebase.login() promise... by the time I get to the dispatch of .push navigation, the PrivateRoute has re-rendered without the new auth object and never re-renders.
I can step through and watch it. It goes:
Login success
Enter into .then function
Notify user of login success
Change navigation to dashboard view
PrivateRoute component re-renders and see's no UID or auth
firebase.login promise entirely resolved
Application state update completes and firebase.auth now has Uid and all other profile/auth objects in state
Container to react-router BrowserRouter, Switch and Route's runs normally
View attached to this container re-renders fine, down to the function where I return the PrivateRoute components
PrivateRoute render method is never reached on the second render, the one where the firebase.login user data finally makes it into firebase.auth object state. So now I am authorized, however it never see's the new auth object because of no rendering...

React Axios not rendering after setState

I've got troubles with my Login component, I'm using the cointainer pattern, so i've got a component Login which manage the api call and a DumbLogin managing the input and warn the user about the response from my server.
I'm using Axios to do the api call, which use promises to do that, I'm also using arrow function to avoid troubles wiht the this, well that's what i was thinking...
Because in the end I have
TypeError: _this.setState is not a function
I tried to use a variable self to resolve that but that result in the same error.
How Am I suppose to do?
axios(all)
.then((response) => {
console.log(response);
if (response.status === 200){
this.setState({
...this.state,
spin: false,
});
let data = response.data;
this.props.connect(data.username, data.email, data.token, data.partners);
this.props.router.push('panel');
}
}).catch((error) => {
console.log(error.response);
this.setState({
spin: false,
errorLogin: 'Username or password wrong',
});
});
(spin is a boolean to inform DumbLogin that it should or not display a spinner while waiting for the response from my server)
it's because the this inside of the anonymous function inside of the then is not the this of the React component.
you need to bind the correct this(Reac Component's this) for the function which does the ajax call.
eg. if the function is:
fetchData(){
axios(all).then(...).catch(...)
}
you can bind it in the constructor:
constructor(){
this.fetchData = this.fetchData.bind(this)
}
HOWEVER!
This is not the recommended solution, you would usually dont want to do ajax call in the component... you may want redux, mobx or even redux-saga for this purpose

ReactJS recieving form errors from server

Below is my signup function in React. How can I render the errors it recieves from backend ? I tried to put a this.setResponse into the catch part ... that didn't work. I understand that componentWillReceiveProps should handle updates but this is an update from a Service (also below) not from a parent component.
If I print the err I can see a dictionary with errors, but I don't see how to pass them to the form or to the fields for rendering.
signup(evt) {
evt.preventDefault();
...
Auth.signup(dict)
.catch(function(err) {
alert("There's an error signing up");
console.log(err.response);
});
},
The Auth service is defined like this:
signup(dict) {
console.log(dict);
return this.handleAuth(when(request({
url: UserConstants.SIGNUP_URL,
method: 'POST',
type: 'json',
data: dict
})));
}
And I hoped to send errors to the fields with the following:
<UserNameField
ref="username"
responseErrors={this.responseErrors}
/>
Presuming that UserNameField is in the same component as the auth callback you can just set the state which will trigger a re-render and pass the values through UserNameField as props.
signup(evt) {
evt.preventDefault();
...
Auth.signup(dict)
.catch((err) => {
alert("There's an error signing up");
console.log(err.response);
this.setState({error: err});
});
},
<UserNameField
ref="username"
responseErrors={this.responseErrors}
error={this.state.error}
/>
This is the same as .bind(this).

Resources