I'm trying to update user credentials in a React/Redux project with TypeScript. I'm using Firebase so the user credentials are being returned in the .then()
block of the signInWithPopup
method.
Here is the component. It is broken because it is breaking the rules of hooks. I can't find a workaround. I thought that using useEffect
with a state object named currentUser
would let me run the hook in the root of the functional component after the local (currentUser
) state updates inside of the .then
block.
Functional Component
import React, { useState, useEffect } from 'react';
import {
getAuth,
signInWithPopup,
GithubAuthProvider,
Auth,
} from 'firebase/auth';
import 'bulmaswatch/superhero/bulmaswatch.min.css';
import { storeState } from '../state';
import { UserData } from '../state/action-creators';
import { useTypedSelector } from '../hooks/use-typed-selector';
const UserAuth: React.FC = () => {
const [currentUser, setCurrentUser] = useState<UserData | null>(null);
const [loginButtonText, setLoginButtonText] = useState<string>('Log In');
const provider: GithubAuthProvider = new GithubAuthProvider();
const auth: Auth = getAuth();
useEffect(() => {
if (currentUser !== null) {
useTypedSelector(({ user }) => currentUser);
}
}, [currentUser]);
const signinHandler = () => {
signInWithPopup(auth, provider)
.then(({ user: { uid, email, displayName, photoURL } }) => {
console.log({ uid, email, displayName, photoURL });
const userPayload: UserData = {
uid: uid,
email: email as string,
displayName: displayName as string,
photoURL: photoURL as string,
};
setCurrentUser(userPayload);
console.log({ userPayload });
console.log({ storeState });
})
.catch((err) => {
const errorCode = err.code;
const errorMessage = err.message;
const email = err.email;
const credential = GithubAuthProvider.credentialFromError(err);
// todo - display errors
console.log(err);
console.log(errorCode, errorMessage, email, credential);
});
};
return (
<button className="gh-button button is-primary" onClick={signinHandler}>
<i className="fab fa-github" />
<strong style={{ marginLeft: '10px' }}>{loginButtonText}</strong>
</button>
);
};
export default UserAuth;
This is the useTypedSelector
hook
import { useSelector, TypedUseSelectorHook } from 'react-redux';
import { RootState } from '../state';
export const useTypedSelector: TypedUseSelectorHook<RootState> = useSelector;
This is my userReducer
for context
import produce from 'immer';
import { ActionType } from '../action-types';
import { Action } from '../actions';
interface UserState {
uid: string;
email: string;
displayName: string;
photoURL: string;
}
const initialState: UserState = {
uid: '',
email: '',
displayName: '',
photoURL: '',
};
const reducer = produce((state: UserState = initialState, action: Action) => {
switch (action.type) {
case ActionType.SET_USER:
const { uid, email, displayName, photoURL } = action.payload;
state.uid = uid;
state.email = email;
state.displayName = displayName;
state.photoURL = photoURL;
return state;
default:
return state;
}
});
export default reducer;
Notes
I'm using Immer so I can easily modify the immutable state
If I could just connect my component to the Redux store so that I could dispatch the action directly from my component - that'd be great too.
I'm about ready to switch to the context API because Redux is nowhere as intuitive as Vuex is.