Created
June 10, 2019 00:20
-
-
Save timc1/d559d0f8769b4badc0bfc22484fe97a3 to your computer and use it in GitHub Desktop.
React Context + Hooks + Firebase Authentication
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React from 'react' | |
import firebaseConfig from '../path/to/firebase-config' | |
import firebase from 'firebase/app' | |
import 'firebase/auth' | |
import FullPageLoading from '../path/to/full-page-loading' | |
AuthProvider.actions = { | |
setUser: 'SET_USER', | |
toggleLoading: 'TOGGLE_LOADING', | |
} | |
const reducer = ( | |
state, | |
action | |
) => { | |
switch (action.type) { | |
case AuthProvider.actions.setUser: | |
return { | |
user: action.payload.user, | |
isInitiallyLoading: false, | |
isLoading: false, | |
} | |
case AuthProvider.actions.toggleLoading: | |
return { | |
...state, | |
isLoading: action.payload.value, | |
} | |
default: | |
throw new Error(`No case for type ${action.type} found.`) | |
} | |
} | |
const AuthContext = React.createContext(undefined) | |
export function AuthProvider({ | |
initialUser, | |
children, | |
}) { | |
const [state, dispatch] = React.useReducer(reducer, { | |
isInitiallyLoading: true, | |
isLoading: false, | |
user: null, | |
}) | |
const signingInSoDontDispatchOnAuthStateChange = React.useRef(false) | |
React.useEffect(() => { | |
// Setup Firebase authentication state observer and get user data. | |
if (!firebase.apps.length) { | |
firebase.initializeApp(firebaseConfig) | |
} | |
firebase.auth().onAuthStateChanged(function(user) { | |
if (user) { | |
// User is signed in. | |
if (signingInSoDontDispatchOnAuthStateChange.current) { | |
signingInSoDontDispatchOnAuthStateChange.current = false | |
return | |
} | |
dispatch({ | |
type: AuthProvider.actions.setUser, | |
payload: { | |
user, | |
}, | |
}) | |
} else { | |
// User is signed out. | |
dispatch({ | |
type: AuthProvider.actions.setUser, | |
payload: { | |
user: null, | |
}, | |
}) | |
} | |
}) | |
}, []) | |
const signup = (email, password, displayName) => { | |
signingInSoDontDispatchOnAuthStateChange.current = true | |
toggleLoading(true) | |
let user: any | |
firebase | |
.auth() | |
.createUserWithEmailAndPassword(email, password) | |
.then(() => { | |
user = firebase.auth().currentUser | |
user.sendEmailVerification() | |
}) | |
.then(() => { | |
user.updateProfile({ | |
displayName, | |
}) | |
}) | |
.then(() => { | |
toggleLoading(false) | |
// Set user with displayName here because user.updateProfile | |
// is async and our onAuthStateChanged listener will fire | |
// before the user is updated. When that happens, user.displayName | |
// value will be null. | |
// Reference: https://github.com/firebase/firebaseui-web/issues/36 | |
const updatedUserWithDisplayName = { | |
...user, | |
displayName, | |
} | |
dispatch({ | |
type: AuthProvider.actions.setUser, | |
payload: { | |
user: updatedUserWithDisplayName, | |
}, | |
}) | |
}) | |
.catch(function(error) { | |
// Handle Errors here. | |
const errorCode = error.code | |
const errorMessage = error.message | |
console.log('errorCode', errorCode, 'errorMessage', errorMessage) | |
toggleLoading(false) | |
}) | |
} | |
const signin = (email, password) => { | |
toggleLoading(true) | |
firebase | |
.auth() | |
.signInWithEmailAndPassword(email, password) | |
.then(() => { | |
toggleLoading(false) | |
}) | |
.catch(function(error) { | |
// Handle Errors here. | |
const errorCode = error.code | |
const errorMessage = error.message | |
console.log('errorCode', errorCode, 'errorMessage', errorMessage) | |
toggleLoading(false) | |
}) | |
} | |
const signout = () => { | |
toggleLoading(true) | |
firebase | |
.auth() | |
.signOut() | |
.then(function() { | |
// Sign-out successful. | |
toggleLoading(false) | |
}) | |
.catch(function(error) { | |
// An error happened. | |
toggleLoading(false) | |
}) | |
} | |
const sendResetPasswordEmail = (email) => { | |
toggleLoading(true) | |
firebase | |
.auth() | |
.sendPasswordResetEmail(email) | |
.then(function() { | |
// Email sent. | |
toggleLoading(true) | |
// TODO: Toggle success notification here. | |
}) | |
.catch(function(error) { | |
// An error happened. | |
console.log('error', error) | |
toggleLoading(false) | |
}) | |
} | |
const toggleLoading = (isLoading) => { | |
dispatch({ | |
type: AuthProvider.actions.toggleLoading, | |
payload: { | |
value: isLoading, | |
}, | |
}) | |
} | |
const value = { | |
user: initialUser || state.user, | |
signup, | |
signin, | |
signout, | |
sendResetPasswordEmail, | |
isLoading: state.isLoading, | |
} | |
return state.isInitiallyLoading ? ( | |
<FullPageLoading /> | |
) : ( | |
<AuthContext.Provider value={value}>{children}</AuthContext.Provider> | |
) | |
} | |
export default function useAuth() { | |
const context = React.useContext(AuthContext) | |
if (context === undefined) { | |
throw new Error('useAuth must be used within an AuthProvider') | |
} | |
return context | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment