Skip to content

Instantly share code, notes, and snippets.

@ryanflorence
Last active December 20, 2021 09:46
Show Gist options
  • Save ryanflorence/eba97731b5579a1c01702c9d394b3feb to your computer and use it in GitHub Desktop.
Save ryanflorence/eba97731b5579a1c01702c9d394b3feb to your computer and use it in GitHub Desktop.
let UserContext = React.createContext();
class App extends React.Component {
state = {
user: null,
setUser: user => {
this.setState({ user });
}
};
render() {
return (
<UserContext.Provider value={this.state}>
<Router>
<Home path="/" />
<About path="/about" />
<PrivateRoute as={Dashboard} path="/dashboard" />
</Router>
</UserContext.Provider>
);
}
}
class PrivateRoute extends React.Component {
static contextType = UserContext;
render() {
let { as: Comp, ...props } = this.props;
return this.context.user ? <Comp {...props} /> : <Login />;
}
}
class Login extends React.Component {
static contextType = UserContext;
render() {
return (
<form
onSubmit={async () => {
let user = await doWhateverYouNeedToDoToLogin();
this.context.setUser(user);
}}
/>
);
}
}
function Home() {
return <div>home</div>;
}
function About() {
return <div>about</div>;
}
function Dashboard() {
return <div>Protected dashboard</div>;
}
@B-R-Bender
Copy link

B-R-Bender commented Oct 15, 2019

It won't work if we try to nest some component inside of protected component:

    function Dashboard() {
        return (
            <div>
                <Link to={"/home"}>GoHome</Link>
                Protected nested dashboard route won't work - Home component renders anyway
                <Home path={"/home"} />
            </div>
        );
    }

is there a way to get this code works?

@jeud
Copy link

jeud commented Jan 3, 2020

Hi

class PrivateRoute extends React.Component {
  static contextType = UserContext;

  render() {
    let { as: Comp, ...props } = this.props;
    return this.context.user ? <Comp {...props} /> : <Login />;
  }
}

to me, returning <Login /> doesn't change the URL on the browser, unfortunately the <Redirect /> of the @reach/router serves different purpose from the react-router's

please guide
thanks

@azicchetti
Copy link

azicchetti commented Feb 3, 2020

Instead of writing a custom component, I prefer to wrap the ones requiring authentication with a HOC.
This has been tested with purely functional components and hooks.

I'm using a store with MobX but this can be easily rewritten using just props.
I decided to use props.navigate instead of <Redirect /> because I can put the original url in location.state.from.
Upon a successful login, I can redirect the user back to the requested path.

const requiresAuthentication = (WrappedComponent) => {
        return observer(
                (props) => {
                        const store = useContext(StoreContext);

                        useEffect( () => {
                                if (!store.isAuthenticated) {
                                        //console.log('not authenticated');
                                        props.navigate('/login', { state: { from: props.uri } });
                                }
                        });

                        return (
                        <React.Fragment>
                        { store.isAuthenticated &&
                        <WrappedComponent {...props} />
                        }
                        </React.Fragment>
                        );
                }
        );
}

export default requiresAuthentication;

Usage (remove the observer wrapper if you're not using MobX):

const Products = requiresAuthentication( observer( (props) => { ... my functional component }) );

@christensen143
Copy link

@DevanB - I think this is what you were curious about.

// AuthContext

export const AuthContext = React.createContext();

export const AuthProvider = AuthContext.Provider;
export const AuthConsumer = AuthContext.Consumer;


// App.js

const App = () => {
  const [user, setUser] = useState(null);
  
  const updateUser = newUser => {
    setUser(newUser);
  }
  
  return (
    <AuthProvider
      value={{
        user,
        updateUser
      }}
    >
      <Router>
        <Home path="/" />
	<About path="/about" />
	<PrivateRoute as={Dashboard} path="/dashboard" />
      </Router>
    </AuthProvider>
  );
}


// PrivateRoute.js

const PrivateRoute = props => {
  const { user } = useContext(AuthContext);
  
  let { as: Comp, ...props } = props;
  return user ? <Comp {...props} /> : <Login />;
}


// Login.js

const Login = () => {
  const { user, updateUser } = useContext(AuthContext);
  
  return (
    <form
      onSubmit={async () => {
        let newUser = await doWhateverYouNeedToDoToLogin();
	  updateUser(newUser);
      }}
    />
  );
}


// Home.js

const Home = () => {
  return <div>home</div>;
}


// About.js

const About = () => {
  return <div>about</div>;
}


// Dashboard.js

const Dashboard = () => {
  return <div>Protected Dashboard</div>;
}

@toroduque
Copy link

toroduque commented Feb 26, 2020

I am having issues with implementing this with nested routes. Can anyone guide me how should i go for nested private routes ? I tried nesting inside the component with router but it just doesn't render the component at all.

For nested private routes you can pass the children:

class PrivateRoute extends React.Component {
  static contextType = UserContext;

  render() {
    let { as: Comp, children, ...props } = this.props;
    return this.context.user ? <Comp {...props} >{children}</Comp> : <Login />;
  }
}

@dgtlmonk
Copy link

dgtlmonk commented Mar 5, 2020

@joaodlf

+1 for Hooks, but would also love to see how this would work with purely functional components!

  const Dashboard = () => <div>dashboard</div>;
  const ProtectedRoute = ({ user }) => {
    return user != null ? (
      <Router>
        <Dashboard path="/"></Dashboard>
      </Router>
    ) : (
      <Login  />
    );
  };

  return (
     <AuthProvider
      value={{
        user,
        updateUser
      }}
    >
      <Router>
        <ProtectedRoute path="/" user={user} />
      </Router>
    </AuthProvider>
  );
}

@cvrajeesh
Copy link

this is how I implemented PrivateRoute

const PrivateRoute = props => {
  const { user } = useUserContext();
  let { as: Comp, ...otherProps } = props;

  return user ? (
    <Comp {...otherProps} />
  ) : (
    <Redirect to="/login" replace={true} noThrow={true} />
  );
};

Make sure noThrow props is set to true otherwise you'll get exception when redirecting in development.

@markuckermann
Copy link

@christensen143 I like your hooks example but think

// PrivateRoute.js

const PrivateRoute = props => {
  const { user } = useContext(AuthContext);
  
  let { as: Comp, ...props } = props;
  return user ? <Comp {...props} /> : <Login />;
}

Should be

// PrivateRoute.js

const PrivateRoute = props => {
  const { user } = useContext(AuthContext);
  
  let { as: Comp, ...restOfTheProps } = props;
  return user ? <Comp {...restOfTheProps } /> : <Login />;
}

to avoid an Identifier 'props' has already been declared error.

@harenadata
Copy link

Make sure noThrow props is set to true otherwise you'll get exception when redirecting in development.

@cvrajeesh you fixed my problem. thank you!

@azamatsmith
Copy link

For the people that show up here for how to do this with nested routing, I solved it using nested routers:

// App.js

<Router>
   <Login path="/login" />
   <ForgotPass path="/forgot-password" />
   <PrivateRoute as={Dashboard} path="/dashboard/*" />
</Router>

// Dashboard
export default ({children}) => (
  <Router>
    <DashHome path="/" />
    <DashStatistics path="/statistics" />
  </Router>
)

@shennan
Copy link

shennan commented Nov 20, 2020

New to this. My <Router> moans that I don't have path as a prop in the children components I pass. What happens when you have dynamically created pages (like blog posts) in your gatsby-node.js? Can't I just have...

<Router>
  {authenticated && <PrivateRoute/>}
  {!authenticated && <PublicRoute/>}
</Router>

...and then not have to think about it further down the component tree? Rather than pepper my whole application with <Component path="path/to/something"/>?

Also, does <Router/> effectively stop Gatsby from serving static files?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment