Description
Hi!
I’m starting with React Basic Hooks those days for a Firebase project for my company and I find the package really amazing.
But I’m struggling with a specific point (not a really blocking point btw) : I’m using a Reducer and a Context at a root level component in order to require it from anywhere, and it’s working great. I’m using Firebase Authentication and I’m listening to the authentication which signals the application when a user logs in or out to ping the store and update it accordingly. But strangely, for the user auth, the reducer gets called twice while the dispatch is called once.
Here’s a Minimal Reproducible Example:
-- Main.purs
module Main where
import Prelude
import Data.Maybe (Maybe(..), maybe)
import Data.Nullable (Nullable)
import Data.Nullable as Nullable
import Effect (Effect)
import Effect.Class.Console as Console
import Effect.Exception (throw)
import Effect.Unsafe (unsafePerformEffect)
import React.Basic.DOM (render)
import React.Basic.DOM as DOM
import React.Basic.Events (handler_)
import React.Basic.Hooks (type (/\), Component, ReactContext, component, useEffectOnce, useReducer, (/\))
import React.Basic.Hooks as RB
import React.Basic.Hooks as React
import Web.DOM.NonElementParentNode (getElementById)
import Web.HTML as HTML
import Web.HTML.HTMLDocument (toNonElementParentNode)
import Web.HTML.Window as Window
type User = { uid :: String }
foreign import foreignAuthStateChanged :: (Nullable User -> Unit) -> Effect (Effect Unit)
foreign import foreignSignInWithPopup :: String -> Array String -> Effect Unit
foreign import foreignSignOut :: Effect Unit
type State = { user :: Maybe User}
data Action
= UpdateUser (Maybe User)
init :: State
init = { user: Nothing }
reduce :: State -> Action -> State
reduce state (UpdateUser user) =
let _ = unsafePerformEffect $ Console.warn "UpdateUser" in
state { user = user }
onAuthStateChanged :: (Maybe User -> Effect Unit) -> Effect (Effect Unit)
onAuthStateChanged callback =
foreignAuthStateChanged $ Nullable.toMaybe >>> callback >>> unsafePerformEffect
context :: ReactContext (State /\ (Action -> Effect Unit))
context =
unsafePerformEffect
$ RB.createContext
$ init /\ (const $ Console.warn "Nothing implemented")
mkApp :: Component Unit
mkApp = do
component "App" \props -> React.do
store <- useReducer init reduce
let state /\ dispatch = store
useEffectOnce $ onAuthStateChanged $ \user -> do
Console.warn "Dispatching"
dispatch $ UpdateUser user
pure $ RB.provider context store
[ DOM.pre_ [DOM.text $ show state.user]
, DOM.button
{ onClick: handler_ $ maybe (foreignSignInWithPopup "Google" ["openid"]) (const foreignSignOut) state.user
, children:
[ DOM.text $ maybe "Connect" (const "Disconnect") state.user]
}
]
main :: Effect Unit
main = do
document <- Window.document =<< HTML.window
container <- getElementById "app" $ toNonElementParentNode document
case container of
Nothing -> throw "No Element \"app\" found."
Just node -> do
root <- mkApp
render (root unit) node
// Main.js
exports.foreignAuthStateChanged = callback => () => {
return firebase.auth().onAuthStateChanged(callback)
}
const selectProvider = provider => {
switch (provider) {
case 'Google':
return new firebase.auth.GoogleAuthProvider()
}
}
exports.foreignSignInWithPopup = provider => scopes => () => {
const authProvider = selectProvider(provider)
scopes.forEach(scope => authProvider.addScope(scope))
return firebase.auth().signInWithPopup(authProvider)
}
exports.foreignSignOut = () => {
return firebase.auth().signOut()
}
When running this, we got:
Is there anything I’m doing wrong or is it a React problem? I saw that React.StrictMode sometimes calls the reducer twice, but I don’t use it… Because everything is pure it’s not so painful, but I don’t understand what’s happening.
For information, connect / disconnect just run the reducer once after the first time. It’s really only the first time. I also tried it in pure React and couldn’t reproduce the bug.
I created a repo with the example in order to test it easily: ghivert/example-bug-purs.