React+Redux+Firebase刷新页面报无法读取null的displayName错误
问题根因
Firebase Auth 初始化是异步过程:页面刷新/首次加载时,SDK需要和服务端校验本地存储的登录凭证,校验完成前firebase.auth().currentUser固定为null。
你当前代码有两个直接触发报错的问题:
onAuthStateChanged状态监听器写在useEffect里,等监听器触发、把用户信息同步到Redux的时机,晚于子组件UserInfo的首次渲染,首次渲染时拿到的user值就是null,直接读取null.displayName必然抛错。- 路由层没有做Auth加载态拦截,Auth校验没完成就直接渲染了业务页面,中间空窗期必然会读到空的用户值。哪怕你不走Redux,直接读
firebase.auth().currentUser,在SDK初始化完成前这个值也是null,一样会报错。
修复方案
1. 所有读取用户属性的位置加空值兜底
修改UserInfo组件,用户信息未加载完成时不要读取用户属性,返回加载占位即可:
const UserInfo = (props) => { // 用户状态未就绪时直接返回加载占位,不执行后续属性读取 if (!props.user) { return <div>加载中...</div> } return ( <div> <Box display={'flex'} flexDirection={'row'} alignItems='center' justifyContent={'center'} color='white' paddingTop={2} paddingBottom={2} sx={{height: '100%', flex: 1}} > <Avatar alt='profile-pic' src={props.user.photoURL} /> <Typography sx={{ml: 10}} > {props.user.displayName} </Typography> </Box> </div> ) } const mapStateToProps = (state) => { return { user: state.user.currentUser } } export default connect(mapStateToProps)(UserInfo)
哪怕你直接调用firebase.auth().currentUser读取数据,也必须先判断这个值是否存在,不能直接链式读属性,这是必加的防御性逻辑。
2. 路由层增加Auth加载态拦截
首先在user reducer中新增authLoading字段,初始值设为true,每次setUser action触发时同步把authLoading设为false。
之后修改IndexRoutes逻辑,Auth状态校验完成前不要渲染路由内容,同时补上监听器的卸载逻辑避免内存泄漏:
const IndexRoutes = (props) => { useEffect(() => { // 注册Auth状态监听,校验完成后自动触发回调 const unsubscribe = firebase.auth().onAuthStateChanged((user) => { props.setUser(user) if (user) { props.history.push("/"); } else { props.history.push("/login"); } }) // 组件卸载时取消监听,避免内存泄漏 return () => unsubscribe() }, []); // Auth校验未完成时,全局展示加载态,不渲染路由规则 if (props.authLoading) { return <div>页面加载中...</div> } return ( <Switch> <Route path='/login' component={Login} /> <Route path='/register' component={Register} /> <Route path='/' component={App} /> </Switch> ) } const mapStateToProps = (state) => { return { currentUser: state.user.currentUser, authLoading: state.user.authLoading } } const mapDispatchToProps = (dispatch) => { return { setUser: (user) => {dispatch(setUser(user))} } } const IndexRouter = withRouter(connect(mapStateToProps, mapDispatchToProps)(IndexRoutes))
额外注意事项
- 不要把
firebase.auth().currentUser作为首屏渲染的数据源,这个属性不是响应式的,只有onAuthStateChanged回调返回的用户状态是可靠的。 - 即使用户处于登录状态,
displayName和photoURL也可能为空(比如邮箱密码注册的用户默认不会设置这两个字段),建议给这两个字段也加兜底值:比如头像用默认图,昵称显示默认文案,避免后续出现同类空值报错。
内容的提问来源于stack exchange,提问作者san
相关产品推荐
相关产品推荐

