React Context API alternative3 months ago

experimentreactcontextobservable

For my next React project, I need to integrate Firebase. Let's start with the authentication system:

With React Context API

1
2
3
4
5
6
7
// context.js

export const AuthContext = createContext()

export function useAuthContext() {
  return useContext(AuthContext)
}
1
2
3
4
5
6
7
8
// service.js

export function onAuthStateChanged(callback) {
  return firebase.auth().onAuthStateChanged((user, error) => {
    if (error || !user) callback(false)
    else callback(user)
  })
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// provider.jsx

import {AuthContext} from './context'
import {onAuthStateChanged} from './service'

function AuthProvider({children}) {
  const [auth, setAuth] = useState(null)

  useEffect(() => {
    const unsubscribe = onAuthStateChanged(setAuth)
    return () => unsubscribe()
  }, [])

  return (
    <AuthContext.Provider value={auth}>
      {children}
    </AuthContext.Provider>
  )
}

export default AuthProvider

I'm quite happy, it works well. I re-read a bit their documentation, and I see in this paragraph the word observer. It reminds me of RxJS, with their Observables. Wait... what about replacing my context with an observable?

With RxJS Observable pattern

I have less files and less code:

1
2
3
4
5
6
7
8
9
10
// service.js

export const user$ = new BehaviorSubject(null)

export function onAuthStateChanged(callback) {
  return firebase.auth().onAuthStateChanged((user, error) => {
    if (error || !user) user$.next(false)
    else user$.next(user)
  })
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// hooks.js

import {onAuthStateChanged} from './service'

export function useAuth() {
  const [user, setUser] = useState(user$.value)

  useEffect(() => {
    const subscription = user$.subscribe(setUser)
    return () => subscription.unsubscribe()
  }, [])

  return user
}

export function useAuthService() {
  useEffect(() => {
    const unsubscribe = onAuthStateChanged()
    return () => unsubscribe()
  }, [])
}

Also, my App file is much cleaner. I don't have to indent one level more for each new provider:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// with context
function App() {
  return (
    <AuthProvider>
      <OtherProvider>
        ...
      </OtherProvider>
    </AuthProvider>
  )
}

// with observable
function App() {
  useAuthService()
  useOtherService()

  return (
    ...
  )
}

Conclusion

This is just an experiment but the result is interesting. The code is cleaner and we prevent some of the unexpected re-renders. It could be nice to compare bundle sizes and performances.

I don't see any drawback here, but I may miss something. I would love to get some feedback, feel free to leave a comment!