apollo client query protected route - reactjs

I am trying to apply protected route using private route method in my graphql react app. I am using apollo client 2.
The private route works as intended, that is it protects / prevents someone from manually entering url (in this case /surveys). However, when the page is reloaded or refreshed manually, graphql query will initially return an empty object data (when user is logged in) or undefined data. Because of this, the condition for redirect inside the private route is applied hence the client is redirected to the "/" path. Is there anyway around this? Here is my app and my private route code:
/* app.js */
....imports
const WHOAMI = gql`
query whoAmI {
whoAmI {
_id
email
}
}
`;
class App extends Component {
render() {
return (
<div>
<BrowserRouter>
<div>
<Header />
<Query query={WHOAMI}>
{({ loading, error, data }) => {
// console.log(data);
return (
<div className="container">
<div className="col s12 center-align">
<Switch>
<Route path="/surveys/new" component={SurveyNew} />
<PrivateRoute
path="/surveys"
userId={data.whoAmI}
component={SurveyList}
/>
<Route path="/" exact component={LandingPage} />
</Switch>
</div>
</div>
);
}}
</Query>
</div>
</BrowserRouter>
</div>
);
}
}
export default App;
and my PrivateRouter file
...imports
const PrivateRoute = ({ component: Component, userId, ...rest }) => (
<Route {...rest} render={props => (
!userId || userId === 'undefined' ? (
<Redirect to={{
pathname: '/'
}}
/>
) : (
<Component {...props} />
)
)} />
);
export default PrivateRoute

I have probably the same architecture and I do
class App extends Component {
constructor (...args) {
super(...args)
this.state = {
user: {},
loading: true
}
}
componentDidMount() {
fetchUser()
.then(user => {
this.setState({
user,
loading: false
})
})
}
render () {
if (this.state.loading) {
return <LoadingPage />
} else {
return (
<ApolloProvider client={client}>
<BrowserRouter>
...
<BrowserRouter />
</ApolloProvider>
}
}

Related

Correct way to redirect user to different routes based on authentication in ReactJS

In my ReactJS app, routes are configured in below way:
class App extends React.Component{
constructor(props){
super(props);
this.state={
isLoggedin: false
}
}
componentDidMount(){
if(localStorage.getItem('name'))
{
this.setState({
isLoggedin: true
})}
}
render(){
return(
<>
<Switch>
<Route exact path="/" render={()=><Login isLoggedin={this.state.isLoggedin} />} />
<Route exact path="/login" render={<Login isLoggedin={this.state.isLoggedin} />} />
<Route exact path="/home" render={()=><Home isLoggedin={this.state.isLoggedin} />} />
</Switch></>
);
}
}
In Login.js:
class Login extends React.Component{
render(){
if(this.props.isLoggedin) return <Redirect to="/home" />;
return(
<h1>Login here</h1>
);
}
}
In Home.js:
class Home extends React.Component{
render(){
if(!this.props.isLoggedin) return <Redirect to="/login" />;
return(
<h1>Home</h1>
);
}
}
So what this code will do is that when the user visits the /, it would first go to Login component and as soon as isLoggedin is set to true, it would redirect the user to Home.js. Same thing would happen if user is not loggedin and he tries to access /home, he would be redirected to /login. Since I am using local storage, all of this would happen in flash of eye. It is also working just fine.
But I doubt if it is the best method to achieve my goal. I want to know if there is any more advisable method to do this.
Thanks!!
A more advisable method would be to decouple the auth checks from the components and abstract this into custom route components.
PrivateRoute - if the user is authenticated then a regular Route is rendered and the props are passed through, otherwise redirect to the "/login" path for user to authenticate.
const PrivateRoute = ({ isLoggedIn, ...props }) => {
return isLoggedIn ? <Route {...props} /> : <Redirect to="/login" />;
};
AnonymousRoute - Basically the inverse of the private route. If the user is already authenticated then redirect them to the "/home" path, otherwise render a route and pass the route props through.
const AnonymousRoute = ({ isLoggedIn, ...props }) => {
return isLoggedIn ? <Redirect to="/home" /> : <Route {...props} />;
};
From here you render the Login and Home components into their respective custom route components.
<Switch>
<PrivateRoute
isLoggedIn={this.state.isLoggedIn} // *
path="/home"
component={Home}
/>
<AnonymousRoute
isLoggedIn={this.state.isLoggedIn} // *
path={["/login", "/"]}
component={Login}
/>
</Switch>
* NOTE: The isLoggedIn={this.state.isLoggedIn} prop is only required here since the isLoggedIn state resides in the App component. A typical React application would store the auth state in a React Context or in global state like Redux, and thus wouldn't need to be explicitly passed via props, it could be accessed from within the custom route component.
Full sandbox code:
const PrivateRoute = ({ isLoggedIn, ...props }) => {
return isLoggedIn ? <Route {...props} /> : <Redirect to="/login" />;
};
const AnonymousRoute = ({ isLoggedIn, ...props }) => {
return isLoggedIn ? <Redirect to="/home" /> : <Route {...props} />;
};
class Login extends Component {
render() {
return (
<>
<h1>Login here</h1>
<button type="button" onClick={this.props.login}>
Log in
</button>
</>
);
}
}
class Home extends Component {
render() {
return (
<>
<h1>Home</h1>
<button type="button" onClick={this.props.logout}>
Log out
</button>
</>
);
}
}
export default class App extends Component {
state = {
isLoggedIn: false
};
componentDidMount() {
if (localStorage.getItem("isLoggedIn")) {
this.setState({
isLoggedIn: true
});
}
}
componentDidUpdate(prevProps, prevState) {
if (prevState.isLoggedIn !== this.state.isLoggedIn) {
localStorage.setItem("isLoggedIn", JSON.stringify(this.state.isLoggedIn));
}
}
logInHandler = () => this.setState({ isLoggedIn: true });
logOutHandler = () => this.setState({ isLoggedIn: false });
render() {
return (
<div className="App">
<div>Authenticated: {this.state.isLoggedIn ? "Yes" : "No"}</div>
<ul>
<li>
<Link to="/">/</Link>
</li>
<li>
<Link to="/home">home</Link>
</li>
<li>
<Link to="/login">log in</Link>
</li>
</ul>
<Switch>
<PrivateRoute
isLoggedIn={this.state.isLoggedIn}
path="/home"
render={() => <Home logout={this.logOutHandler} />}
/>
<AnonymousRoute
isLoggedIn={this.state.isLoggedIn}
path={["/login", "/"]}
render={() => <Login login={this.logInHandler} />}
/>
</Switch>
</div>
);
}
}

How to set global prop in react apollo client

I am working with react and react-apollo client.
Here is my main route file
const PrivateRoute = ({ component, isAuthed, ...rest }) => {
console.log({ isAuthed })
return (
<Route {...rest} exact
render = {(props) => (
isAuthed ? (
<div>
{React.createElement(component, props)}
</div>
) :
(
<Redirect
to={{
pathname: '/login',
state: { from: props.location }
}}
/>
)
)}
/>
)
}
class App extends Component {
state = {
isAuthed: false
}
async componentWillMount() {
this.setState({ isAuthed: true })
}
render() {
const { isAuthed } = this.state
return (
<div style={{ direction: direction }}>
<Header {...this.props} history={history}/>
<Router history={history}>
<Switch>
<Route exact path="/" render={() => <Redirect to="/dashboard" />} />
<Route path="/login" component={Login}/>
<PrivateRoute isAuthed={isAuthed} path="/dashboard" component={Dashboard} />
<PrivateRoute isAuthed={isAuthed} path="/AdminManagement" component={Admin} />
</Switch>
</Router>
</div>
)
}
}
export default compose(
graphql(SET_SESSION, { name: 'setSession' })
)((withNamespaces)('common')(App))
Now when I do login inside the login component I need to set isAuthed to true which is inside my main route file(above one)
How can I do this with react apollo client?
Unfortunately, there's no such thing as a "global" prop in React or Apollo. If you want to achieve something similar to your example (i.e. update the state on the root component from your login component), have you considered passing a method down to said component and firing it when your GraphQL mutation resolves?
I'm going to take a stab at this, but please note this is all pseudo code and just outlines one of the many approaches you could take to address this:
App.js
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
userInfo: {},
authed: false,
}
this.modifyUserInfo = this.modifyUserInfo.bind(this);
}
modifyUserInfo(userInfo) {
this.setState(state => ({
...state,
userInfo,
authed: true,
}))
}
render() {
return (
// Render all your routes and everything...
<LoginComponent loginCb={modifyUserInfo} />
)
}
}
Login Component
const Login = props => {
return (
<Mutation mutation={loginMutation}>
{(login) => {
<form onSubmit={(e) => {
e.preventDefault();
login()
.then(res => {
if (res.userInfo) {
props.loginCb(res.userInfo);
}
})
.catch(err => {
console.log(err)
})
}}>
{/* Add the rest of your login form */}
<button type="submit"/>
</form>
}}
</Mutation>
)
}
Rather than storing your user authentication information in your root state, have you considered using your Apollo Cache and injecting the user information into the relevant components? Like I said, there are many, many different ways to approach this.

React render private route after redux(user authentication) is updated

PrivateRoute is rendering before Redux gets current user data from the server. What is the best way to fix this issue?
The component looks like below. userAuth.isAuthenticated eventually updates to true but it renders <Redirect to="/login" /> first before it gets updated.
const userAuth = {
isAuthenticated: false
}
const PrivateRoute = ({ component: Component, ...rest }) => (
<Route {...rest} render={(props) => (
// PROBLEM: this renders before Redux gets user authentication data from the server
userAuth.isAuthenticated ? <Component {...props} /> : <Redirect to="/login" />
)}/>
)
class App extends Component {
componentDidMount() {
// Get current user data when user refresh the browser
// This sets isLoginSuccess: true is current user is logged-in in Redux store
this.props.getCurrentUserSession();
}
render() {
// Show loading icon while fetching current user data
if(this.props.user.isFetchingCurrentUserSession){
return (<div><img src={squareLogo} className="logo-loading" alt="Loading icon" /></div>);
}
// Set current user authentication status
userAuth.isAuthenticated = this.props.user.isLoginSuccess
return (
<Router>
<div>
<Route path="/public" component={Public} />
<PrivateRoute path="/private" component={Private} />
</div>
</Router>
);
}
}
function mapStateToProps(state) {
return state
}
function mapDispatchToProps(dispatch) {
return bindActionCreators({
getCurrentUserSession
}, dispatch);
}
export default connect(mapStateToProps, mapDispatchToProps)(App)
You can do like this:
1.Intialize isAuthenticated to null
2.in render return use conditional rendering of the private component
const userAuth = {
isAuthenticated: null
}
in App render return:
return (
<Router>
<div>
<Route path="/public" component={Public} />
{(userAuth.isAuthenticated!==null)?<PrivateRoute path="/private"
component={Private} />:null}
</div>
</Router>
);

React route authentication

I am trying to create a server-side auth guard for react route. My flow is like... Whenever a route is hit on the front-end, a backend call is made to check if the user's session is present in the Database (since we store session in the database to keep track of sessions).
Here is my app component code:
export default function App() {
const routeComponents = routes.map(({ path, component }) => <AuthenticatedRoute exact path={path} component={component} props={'exact'} key={path} />);
return (
<div>
{window.location.origin === constant.PRODUCTION_DOMAIN_NAME && <Route
path="/"
render={({ location }) => {
if (typeof window.ga === 'function') {
window.ga('set', 'page', location.pathname + location.search);
window.ga('send', 'pageview');
}
return null;
}}
/>}
<Switch>
<Route path="/login" component={LoginPage} />
{routeComponents}
</Switch>
<ToastContainer autoClose={constant.TOASTER_FACTOR} closeButton={false} hideProgressBar />
</div>
);
}
App.propTypes = {
component: PropTypes.any,
};
I have segregated the authenticated route into a separate class component
like:
export class AuthenticatedRoute extends React.Component {
componentWillMount() {
//I call backend here to check if user is authenticated by session Id in DB
}
render() {
const { component: Component, ...rest } = this.props;
return (
<Route exact {...rest} render={(props) => this.state.isAuthenticated ? <Component {...props} /> : <Redirect to="/login" />} />
);
}
}
AuthenticatedRoute.propTypes = {
component: PropTypes.any,
....
};
const mapStateToProps = (state) => ({
reducer: state.get('myReducer'),
});
export default connect(mapStateToProps, { func })(AuthenticatedRoute);
But I face an issue where the login page is redirected twice. Could someone let me know a better approach for this?

Auth Protected Routes React Router V4 - Passing Props

I have created a reactJS app using create-react-app using Flux as an architecture where I want to have some routes accessible without being authenticated and some only accessible while authenticated. Using the flux design pattern I am passing the state of the store down through each component using props so the store state is available for all child components that require it.
I have studied the documentation here (the example is also pasted below) to try and understand how to achieve the above outcome in my app.
I can't see how I can adapt the example to pass state down to the component called within the protected route without doing this with an explicit name like how component is passed.I want to acheive...
Pass the component to PrivateRoute so it can be called if my user is authenticated.
Pass all props from the parent component to the component called by PrivateRoute (this is to allow me to keep cascading the store state down through the props and also to check in the store state if the user is logged in).
I think I am perhaps misunderstanding something fundamental here. Can anyone advise please?
import React from "react";
import {
BrowserRouter as Router,
Route,
Link,
Redirect,
withRouter
} from "react-router-dom";
////////////////////////////////////////////////////////////
// 1. Click the public page
// 2. Click the protected page
// 3. Log in
// 4. Click the back button, note the URL each time
const AuthExample = () => (
<Router>
<div>
<AuthButton />
<ul>
<li>
<Link to="/public">Public Page</Link>
</li>
<li>
<Link to="/protected">Protected Page</Link>
</li>
</ul>
<Route path="/public" component={Public} />
<Route path="/login" component={Login} />
<PrivateRoute path="/protected" component={Protected} />
</div>
</Router>
);
const fakeAuth = {
isAuthenticated: false,
authenticate(cb) {
this.isAuthenticated = true;
setTimeout(cb, 100); // fake async
},
signout(cb) {
this.isAuthenticated = false;
setTimeout(cb, 100);
}
};
const AuthButton = withRouter(
({ history }) =>
fakeAuth.isAuthenticated ? (
<p>
Welcome!{" "}
<button
onClick={() => {
fakeAuth.signout(() => history.push("/"));
}}
>
Sign out
</button>
</p>
) : (
<p>You are not logged in.</p>
)
);
const PrivateRoute = ({ component: Component, ...rest }) => (
<Route
{...rest}
render={props =>
fakeAuth.isAuthenticated ? (
<Component {...props} />
) : (
<Redirect
to={{
pathname: "/login",
state: { from: props.location }
}}
/>
)
}
/>
);
const Public = () => <h3>Public</h3>;
const Protected = () => <h3>Protected</h3>;
class Login extends React.Component {
state = {
redirectToReferrer: false
};
login = () => {
fakeAuth.authenticate(() => {
this.setState({ redirectToReferrer: true });
});
};
render() {
const { from } = this.props.location.state || { from: { pathname: "/" } };
const { redirectToReferrer } = this.state;
if (redirectToReferrer) {
return <Redirect to={from} />;
}
return (
<div>
<p>You must log in to view the page at {from.pathname}</p>
<button onClick={this.login}>Log in</button>
</div>
);
}
}
export default AuthExample;
Here is a section from my actual code...
class Page extends React.Component{
// constructor(props) {
// super(props);
// }
render(){
return(
<Router>
<div>
<Route exact path="/" render={()=><HomePage {...this.props}/>}/>
<Route path="/login" render={()=><LoginPage {...this.props}/>}/>
<PrivateRoute exact path="/protected" component={Main} extra="Boo!"/>
</div>
</Router>);
}
}
const PrivateRoute = ({ component: Component, ...rest }) => (
<Route
{...rest}
render={(props) =>
(console.log(this.props.extra) || 1) ? (
<Component {...props} />
) : (
<Redirect
to={{
pathname: "/login",
state: { from: props.location }
}}
/>
)
}
/>
);
This.props.extra is undefined.
If you are looking for a way to pass extra props to the PrivateRoute you can do:
const PrivateRoute = ({ component: Component, ...rest }) => (
<Route
{...rest}
render={ (props) =>
( console.log(props.extra) || 1) ? (
<Component {...props} {...rest} />
) : (
<Redirect
to={{
pathname: "/login",
state: { from: props.location }
}}
/>
)
}
/>
);
then
<PrivateRoute exact path="/protected" component={Main} extra="Boo!"/>
and Main should now receive extra prop (together with path and exact).

Resources