Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
React Hook recipe from https://usehooks.com
// Top level App component
import React from "react";
import { ProvideAuth } from "./use-auth.js";
function App(props) {
return (
<ProvideAuth>
{/*
Route components here, depending on how your app is structured.
If using Next.js this would be /pages/_app.js
*/}
</ProvideAuth>
);
}
// Any component that wants auth state
import React from "react";
import { useAuth } from "./use-auth.js";
function Navbar(props) {
// Get auth state and re-render anytime it changes
const auth = useAuth();
return (
<NavbarContainer>
<Logo />
<Menu>
<Link to="/about">About</Link>
<Link to="/contact">Contact</Link>
{auth.user ? (
<Fragment>
<Link to="/account">Account ({auth.user.email})</Link>
<Button onClick={() => auth.signout()}>Signout</Button>
</Fragment>
) : (
<Link to="/signin">Signin</Link>
)}
</Menu>
</NavbarContainer>
);
}
// Hook (use-auth.js)
import React, { useState, useEffect, useContext, createContext } from "react";
import * as firebase from "firebase/app";
import "firebase/auth";
// Add your Firebase credentials
firebase.initializeApp({
apiKey: "",
authDomain: "",
projectId: "",
appID: ""
});
const authContext = createContext();
// Provider component that wraps your app and makes auth object ...
// ... available to any child component that calls useAuth().
export function ProvideAuth({ children }) {
const auth = useProvideAuth();
return <authContext.Provider value={auth}>{children}</authContext.Provider>;
}
// Hook for child components to get the auth object ...
// ... and re-render when it changes.
export const useAuth = () => {
return useContext(authContext);
};
// Provider hook that creates auth object and handles state
function useProvideAuth() {
const [user, setUser] = useState(null);
// Wrap any Firebase methods we want to use making sure ...
// ... to save the user to state.
const signin = (email, password) => {
return firebase
.auth()
.signInWithEmailAndPassword(email, password)
.then(response => {
setUser(response.user);
return response.user;
});
};
const signup = (email, password) => {
return firebase
.auth()
.createUserWithEmailAndPassword(email, password)
.then(response => {
setUser(response.user);
return response.user;
});
};
const signout = () => {
return firebase
.auth()
.signOut()
.then(() => {
setUser(false);
});
};
const sendPasswordResetEmail = email => {
return firebase
.auth()
.sendPasswordResetEmail(email)
.then(() => {
return true;
});
};
const confirmPasswordReset = (code, password) => {
return firebase
.auth()
.confirmPasswordReset(code, password)
.then(() => {
return true;
});
};
// Subscribe to user on mount
// Because this sets state in the callback it will cause any ...
// ... component that utilizes this hook to re-render with the ...
// ... latest auth object.
useEffect(() => {
const unsubscribe = firebase.auth().onAuthStateChanged(user => {
if (user) {
setUser(user);
} else {
setUser(false);
}
});
// Cleanup subscription on unmount
return () => unsubscribe();
}, []);
// Return the user object and auth methods
return {
user,
signin,
signup,
signout,
sendPasswordResetEmail,
confirmPasswordReset
};
}
@talves

This comment has been minimized.

Copy link

talves commented Aug 26, 2019

Great Hook! Could make the hook reusable with my changes here (maybe). What do you think?

https://gist.github.com/talves/803d3193b493f28fa34dd867c5aac049/revisions

@gragland

This comment has been minimized.

Copy link
Owner Author

gragland commented Sep 6, 2019

Thanks! Could you explain how your changes make it more reusable or any other benefits of moving firebase.initializeApp inside useEffect?

@jackguoAtJogg

This comment has been minimized.

Copy link

jackguoAtJogg commented Oct 30, 2019

When I try to reuse it, I get invalid hook call when using useAuth().

@olso

This comment has been minimized.

Copy link

olso commented Nov 15, 2019

Just fyi, this example will not work in SSR, until Suspense is available in react-dom

@gragland

This comment has been minimized.

Copy link
Owner Author

gragland commented Nov 17, 2019

@jackguoAtJogg Haven't seen this issue. Is your code online somewhere I could take a look?
@olso By not work do you mean you're seeing an error or just that user will always be null server-side? It's true that this doesn't support getting the user object server-side.

@jackguoAtJogg

This comment has been minimized.

Copy link

jackguoAtJogg commented Nov 17, 2019

It's working for me! I got it to work.

@jackguoAtJogg

This comment has been minimized.

Copy link

jackguoAtJogg commented Nov 17, 2019

However, I do ran into a weird issue where the context got cached but im not sure if it has to do with this hook.

@gragland

This comment has been minimized.

Copy link
Owner Author

gragland commented Nov 17, 2019

@jackguoAtJogg Hmm I wonder if that was maybe Firebase doing something weird. Anyway, let me know if it seems to be a recurring problem.

@olso

This comment has been minimized.

Copy link

olso commented Nov 17, 2019

@gragland useEffect is a no-op on the server

@olso

This comment has been minimized.

Copy link

olso commented Nov 17, 2019

@olso

This comment has been minimized.

Copy link

olso commented Nov 17, 2019

However, there is a solution https://www.npmjs.com/package/@shopify/react-effect

But you will have to add another render ssr pass and await the results

@gragland

This comment has been minimized.

Copy link
Owner Author

gragland commented Nov 17, 2019

@oslo yeah getting user on server would need to be implemented differently. If using next.js, could be fetched in the getInitialProps method. But I’m also pretty sure firebase stores the token in localStorage, so would also need to grab it and move it into a cookie. Felt like too much complexity to add to this example.

@olso

This comment has been minimized.

Copy link

olso commented Nov 17, 2019

@gragland true, we decided to drop ssr auth state completely due to this for now

@gragland

This comment has been minimized.

Copy link
Owner Author

gragland commented Nov 17, 2019

@olso yeah I’ve more recently decided that ssr auth just isn’t worth the extra complexity. Maybe it will be much simpler down the road with suspense.

@waldothedeveloper

This comment has been minimized.

Copy link

waldothedeveloper commented Jan 2, 2020

n SSR, until Suspense is available in react-dom

@gragland so you mean that this will NOT work on Next JS?
No wonder I keep getting the auth context undefined

Any child component that needs auth object:
import { useAuth } from "./use-auth.js";
const auth = useAuth()

I keep getting auth is undefined which of course comes from line 56 where we create the context but with no value
const authContext = createContext();
So, what do you recommend (since I'm using NextJS), I'm pulling my hair here (and as you see in my avatar I no longer have hair)?
Any solution will be very appreciated!

@waldothedeveloper

This comment has been minimized.

Copy link

waldothedeveloper commented Jan 2, 2020

@gragland
Well...after thinking a bit more I just deleted the coder related to the Context and kept just pretty much the auth hook component, exported it since that hook returns what I need ( user, signIn, signOut methods, etc) and used it in my sign In page.
Now, do you think that the HandleSubmit looks good?
I'm not sure if I should sync/await the SIGN-IN process and then route to the Account page?!

  const handleSubmit = event => {
    event.preventDefault();

    // TO-DO: dont forget to validate the form

    const signInProcess = async () => {
      const result = await auth.signin(email, password);
      return result;
    };

    signInProcess().then(() => {
      return router.push("/account");
    });
  };

Because the signIn method in the Auth hook example it's a promise based function right?
So should I then async/await on my handleSubmit and then push the Account component?

 const signin = (email, password) => {
    return firebase
      .auth()
      .signInWithEmailAndPassword(email, password)
      .then(response => {
        setUser(response.user);
        return response.user;
      });
  };

Thanks!

@gragland

This comment has been minimized.

Copy link
Owner Author

gragland commented Jan 3, 2020

@waldothedeveloper the useAuth hook should always return an auth object, as the value is passed in ProvideAuth on line 62. I just tested with Next.js and I'm getting an auth object on server render, as well as client. Do you by chance have your code online somewhere I could check out?

@waldothedeveloper

This comment has been minimized.

Copy link

waldothedeveloper commented Jan 3, 2020

Hey, @gragland I do, check it out here:
link-to-project
Make sure you check the code from the sign_up_page branch NOT the master.

I am having a hard time to also try to protect the route:
/account on SSR with NextJS...any idea how to protect routes on Next JS?

@gragland

This comment has been minimized.

Copy link
Owner Author

gragland commented Jan 4, 2020

@waldothedeveloper When you originally tested with useProvideAuth and useAuth did you include an _app.js file and wrap all pages with useProvideAuth? For Next.js you'll need to do that so that useAuth can read the context. For example:

import React from "react";
import App from "next/app";
import { ProvideAuth } from "./../util/auth.js";

class MyApp extends App {
  render() {
    const { Component, pageProps } = this.props;

    return (
      <ProvideAuth>
          <Component {...pageProps} />
      </ProvideAuth>
    );
  }
}

export default MyApp;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.