Redux结合Firebase Auth登录状态判断Hook异常问题求助
问题:useAuth Hook无法正确判断用户登录状态
我正在开发一个加密货币资产应用,用于练习Redux与数据状态管理。未注册用户可添加加密货币资产、查看统计数据;注册用户的资产需在重新登录时保留。采用Firebase Authentication做身份认证,Redux管理状态。目前Firebase交互正常,用户创建与登录流程能正确执行,但自定义的useAuth Hook无法正确判断用户是否已登录。
应用外观

Firebase后台状态

控制台输出

Store代码
export const store = configureStore({ reducer: { user: userReducer, }, }); export type RootState = ReturnType<typeof store.getState>; export type AppDispatch = typeof store.dispatch;
User Slice代码
const initialState = { email: null, token: null, id: null, } const userSlice = createSlice({ name: 'user', initialState, reducers: { setUser(state, action) { state.email = action.payload.email; state.token = action.payload.token; state.id = action.payload.id; }, removeUser(state) { state.email = null; state.token = null; state.id = null; } } }) export const {setUser, removeUser} = userSlice.actions; export default userSlice.reducer
useAuth Hook代码
export function useAuth() { const {email, token, id} = useAppSelector(state => state.user); console.log('is auth work', email, token, id) return { isAuth: !!email, email, token, id, }; }
登录表单组件代码
interface LoginFormProps { onSignUp: () => void; onClose: () => void; } type FieldType = { email?: string; password?: string; remember?: string; }; export function LoginForm({ onSignUp, onClose }: LoginFormProps) { const [email, setEmail] = useState(''); const [password, setPassword] = useState(''); const [rememberMe, setRememberMe] = useState(false); const dispatch = useAppDispatch(); const onFinish = (email: string, password: string) => { const auth = getAuth(); signInWithEmailAndPassword(auth, email, password) .then(({user}) => { dispatch(setUser({ isAuth: true, email: user.email, id: user.uid, token: user.refreshToken, })) console.log('user', user); }) .catch(); }; const onFinishFailed = (errorInfo: any) => { console.log('Failed:', errorInfo); }; return ( <Form name="basic" labelCol={{ span: 4 }} wrapperCol={{ span: 18 }} layout="horizontal" initialValues={{ remember: true }} onFinish={() => {onFinish(email, password); onClose()}} onFinishFailed={onFinishFailed} autoComplete="off" > <Form.Item<FieldType> label="Email" name="email" rules={[{ required: true, message: 'Please input your email!' }]} > <Input value={email || ''} onChange={(e) => { setEmail(e.target.value) }} /> </Form.Item> <Form.Item<FieldType> label="Password" name="password" rules={[{ required: true, message: 'Please input your password!' }]} > <Input.Password value={password || ''} onChange={(e) => { setPassword(e.target.value) }} /> </Form.Item> <Form.Item<FieldType> name="remember" valuePropName="checked" wrapperCol={{ span: 18 }} > <Checkbox value={rememberMe} onChange={(e) => { setRememberMe(e.target.checked) }} >Remember me</Checkbox> </Form.Item> <Flex gap={12}> <Form.Item wrapperCol={{ span: 18 }}> <Button type="primary" htmlType="submit"> Log In </Button> </Form.Item> <Button type="primary" onClick={onSignUp}>Sign Up</Button> </Flex> </Form> ) }
Header组件代码
export default function Header() { const { crypto } = useContext(CryptoContext); const [select, setSelect] = useState(false); const [showModal, setshowModal] = useState(false); const [coin, setCoin] = useState<ICryptoCurrency | null>(null); const [showDrawer, setShowDrawer] = useState(false); const [showCoinForm, setShowCoinForm] = useState(false); const [showLoginForm, setShowLoginForm] = useState(false); const [showRegistrationForm, setShowRegistrationForm] = useState(false); const {isAuth, email} = useAuth(); const handleSelect = (value: string) => { const selectedCoin = crypto.find((c) => c.id === value) if (selectedCoin) { setshowModal(true); setCoin(selectedCoin); } } const handleAddAsset = () => { setShowDrawer(true); setShowCoinForm(true); } const handleRegistration = () => { setShowDrawer(true); setShowRegistrationForm(true); setShowCoinForm(false); setShowLoginForm(false); } const handleLogin = () => { setShowDrawer(true); setShowLoginForm(true); setShowRegistrationForm(false); setShowCoinForm(false); } useEffect(() => { const keypress = (event: any) => { if (event.key === '/') { setSelect((prev) => !prev); } } document.addEventListener('keypress', keypress); return () => document.removeEventListener('keypress', keypress); }, []) const formNames = () => { if (showCoinForm) return 'Add Asset' if (showRegistrationForm) return 'Registration' if (showLoginForm) return 'Log In' } return ( <Layout.Header style={headerStyle}> <Select open={select} onClick={() => setSelect((prev) => !prev)} style={{ width: '250px' }} placeholder="press / to open" onSelect={handleSelect} options={crypto.map((coin: ICryptoCurrency) => ( { label: coin.name, value: coin.id, icon: coin.icon } ))} optionRender={(option) => ( <Space> <img src={option.data.icon} style={{ width: '20px', paddingTop: '6px' }} alt={option.data.id} /> <span> {option.data.label}</span> </Space> )} /> <Flex gap={10}> <Button onClick={handleAddAsset} style={addButtonStyle}>Add Asset</Button> {isAuth ? ( <Avatar icon={<UserOutlined />} /> ) : ( <Button onClick={handleLogin} style={addButtonStyle}>Log In</Button> )} </Flex> <Modal open={showModal} style={{ maxWidth: '420px' }} onCancel={() => setshowModal(false)} footer={null}> <CryptoModal coin={coin} /> </Modal> <Drawer width={600} destroyOnClose title={formNames()} onClose={() => { setShowDrawer(false); setShowCoinForm(false); setShowLoginForm(false); setShowRegistrationForm(false) }} open={showDrawer} > {showCoinForm && ( <AddAssetForm onClose={() => { setShowDrawer(false); setShowCoinForm(false) }} /> )} {showLoginForm && ( <LoginForm onSignUp={handleRegistration} onClose={() => setShowDrawer(false)} /> )} {showRegistrationForm && ( <RegistrationForm onLogin={handleLogin} onClose={() => setShowDrawer(false)} /> )} </Drawer> </Layout.Header> ) }
解决方案
1. 移除登录dispatch的冗余参数
LoginForm的onFinish中,dispatchsetUser时传入的isAuth: true是冗余字段(user slice的state中无该属性),直接移除:
dispatch(setUser({ email: user.email, id: user.uid, token: user.refreshToken, }))
2. 监听Firebase Auth状态变化,同步Redux状态
仅在登录时手动dispatch无法覆盖页面刷新、自动登录等场景,需在应用初始化时添加监听:
import { getAuth, onAuthStateChanged } from "firebase/auth"; import { useAppDispatch } from './store'; import { setUser, removeUser } from './userSlice'; function App() { const dispatch = useAppDispatch(); useEffect(() => { const auth = getAuth(); const unsubscribe = onAuthStateChanged(auth, (user) => { if (user) { dispatch(setUser({ email: user.email, id: user.uid, token: user.refreshToken, })); } else { dispatch(removeUser()); } }); return unsubscribe; }, [dispatch]); // ...其他组件代码 }
3. 优化登录状态判断逻辑
Firebase的user.email可能为null(如第三方登录未绑定邮箱),改用uid判断更可靠:
export function useAuth() { const {email, token, id} = useAppSelector(state => state.user); console.log('is auth work', email, token, id) return { isAuth: !!id, // uid是用户唯一标识,不会为null email, token, id, }; }
4. 验证Redux Provider包裹
确保整个应用被Provider包裹,否则组件无法访问Redux状态:
import { Provider } from 'react-redux'; import { store } from './store'; ReactDOM.createRoot(document.getElementById('root')!).render( <Provider store={store}> <App /> </Provider>, );
5. 调整Drawer关闭时机
Redux状态更新是异步的,登录后延迟关闭Drawer,确保Header组件拿到最新状态:
const onFinish = async (email: string, password: string) => { const auth = getAuth(); try { const {user} = await signInWithEmailAndPassword(auth, email, password); dispatch(setUser({ email: user.email, id: user.uid, token: user.refreshToken, })); setTimeout(() => { onClose(); }, 100); } catch (error) { console.error('Login error:', error); } };
内容的提问来源于stack exchange,提问作者Dmitr Zar
相关产品推荐
相关产品推荐

