Skip to content

Instantly share code, notes, and snippets.

@cdock1029
Created June 7, 2018 22:57
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save cdock1029/a98534ff160692177ac90c6e371cdc68 to your computer and use it in GitHub Desktop.
Save cdock1029/a98534ff160692177ac90c6e371cdc68 to your computer and use it in GitHub Desktop.
react components to handle firebase auth
import { app, User } from 'firebase/app'
import React from 'react'
interface AuthContext {
hasLoaded: boolean
user: User | null
claims?: any
logOut?: () => Promise<any>
logIn?: (email: string, password: string) => void
error?: string
clearError?: () => void
}
export interface AuthProps {
authContext: AuthContext
}
const { Provider, Consumer } = React.createContext<AuthContext>({
hasLoaded: false,
user: null,
})
export interface ProviderProps {
firebase: app.App
claims?: string[]
}
export class FirebaseAuthProvider extends React.Component<
ProviderProps,
AuthContext
> {
private unsub: () => void
constructor(props: ProviderProps) {
super(props)
const currentUser = props.firebase.auth!().currentUser
this.state = {
user: currentUser,
hasLoaded: Boolean(currentUser),
logIn: this.logIn,
logOut: this.logOut,
clearError: this.clearError,
}
}
componentDidMount() {
const { firebase, claims } = this.props
const auth = firebase.auth!()
this.unsub = auth.onAuthStateChanged(async user => {
let claimsResult: any
if (user && claims) {
claimsResult = {}
const refresh = !this.state.hasLoaded
const result = await user.getIdTokenResult(refresh)
claims.forEach(claim => {
claimsResult[claim] = result.claims[claim]
})
}
this.setState(() => ({ hasLoaded: true, user, claims: claimsResult }))
})
}
componentWillUnmount() {
if (this.unsub) {
this.unsub()
}
}
logOut = () => this.props.firebase.auth!().signOut()
logIn = (email: string, password: string) => {
this.props.firebase.auth!()
.signInWithEmailAndPassword(email, password)
.catch(error => {
this.setState({ error: error.message })
})
}
clearError = () =>
this.setState(
({ error }: Pick<AuthContext, 'error'>) =>
error ? { error: undefined } : null,
)
render() {
return <Provider value={this.state}>{this.props.children}</Provider>
}
}
type AuthRender = (authContext: AuthContext) => JSX.Element | JSX.Element[]
interface AuthConsumerProps {
render?: AuthRender
children?: React.ReactNode | AuthRender
tag?: React.Component
loading?: any
}
export const FirebaseAuthConsumer = ({
render,
children,
tag,
loading,
...rest
}: AuthConsumerProps) => (
<Consumer>
{(authContext: AuthContext) => {
if (loading && !authContext.hasLoaded) {
const Loading: any = loading
return <Loading />
}
if (render) {
return render(authContext)
}
if (typeof children === 'function') {
return children(authContext)
}
const chillens = React.Children.map(children, child =>
React.cloneElement(child as React.ReactElement<any>, {
...authContext,
...rest,
}),
)
let Comp: any
if (tag) {
Comp = tag
}
return Comp ? <Comp>{chillens}</Comp> : chillens
}}
</Consumer>
)
export default class FirebaseAuth extends React.Component {
static Provider = FirebaseAuthProvider
static Consumer = FirebaseAuthConsumer
}
@cdock1029
Copy link
Author

class App extends React.Component {
  render() {
    return (
      <div className="App">
        <FirebaseAuthConsumer
          loading={() => <h1>Loading..</h1>}
          render={({ user, logIn, logOut, error, clearError }) => {
            return user ? (
              <>
                <Header logOut={logOut} />
                <Router>
                  <Home path="/" />
                  <Dashboard path="dashboard" />
                  <Redirect from="login" to="dashboard" />
                  <NotFound default />
                </Router>
              </>
            ) : (
              <Router>
                <Login
                  path="login"
                  logIn={logIn!}
                  error={error}
                  clearError={clearError!}
                />
                <DelayRedir default to="login" />
              </Router>
            )
          }}
        />
      </div>
    )
  }
}

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