React组件在useCurrentUser Hook值更新时未重渲的问题排查
问题:React中currentUser更新时Main组件不重新渲染
问题概述
在React应用(使用Supabase作为数据库)中出现如下问题:当useCurrentUser() Hook返回的currentUser值变化时,Main组件仅初始渲染一次,后续不再触发重新渲染;但其他使用useCurrentUser()的组件能正常显示更新后的currentUser值,且Main组件中监听currentUser的useEffect仅打印一次currentUser。
相关代码
Main组件
export default function Main() { const [portrait, setPortrait] = useState(false) const [landscape, setLandscape] = useState(false) const [loginVisible, setLoginVisible] = useState(true) const screenRef = useRef() const em = useEm() const currentUser = useCurrentUser() useEffect(() => { console.log(currentUser) if (currentUser?.id) { setLoginVisible(false) } }, [currentUser]) const version = 'v 2.0.0' return ( <SupabaseBoardStateContextProvider sessionId="asdf"> <View style={{ justifyContent: 'center', alignItems: 'center', height: '100%', width: '100%', overflow: 'hidden', flexBasis: 0, flexGrow: 1 }} ref={screenRef} onLayout={() => screenRef.current.measure((x, y, w, h) => { if (w > h) { setLandscape(true) setPortrait(false) } if (h > w) { setLandscape(false) setPortrait(true) } }) } > {portrait && <Portrait />} {landscape && <Landscape />} {loginVisible && <Login close={() => setLoginVisible(false)} />} </View> </SupabaseBoardStateContextProvider> ) }
useCurrentUser Hook
function useCurrentUser() { return useContext(CurrentUserContext) }
useSupabaseUsers Hook
function useSupabaseUsers() { const [users, setUsers] = useState([]) const [currentUser, setCurrentUser] = useState({}) const [userChannel, setUserChannel] = useState() async function checkAnonymousId() { let anon = '' const [[, oldAnon], [, displayName]] = await AsyncStorage.multiGet(['@anonymous_user_id', '@anonymous_user_displayname']) anon = oldAnon if (!oldAnon) { const newAnon = uuid.v4() AsyncStorage.setItem('@anonymous_user_id', newAnon) anon = newAnon } const channel = supabase.channel('*') channel .on('presence', { event: 'sync' }, () => { const state = channel.presenceState() const keys = Object.values(state).map(([user]) => user) setCurrentUser(keys.filter(x => x.anon_key === anon)[0]) setUsers(keys) }) .subscribe(async status => { if (status === 'SUBSCRIBED') { await channel.track({ anon_key: anon, anon_displayName: displayName }) supabase.auth.getUser().then(handleAuth) supabase.auth.onAuthStateChange(() => { supabase.auth.getUser().then(handleAuth) }) } }) function handleAuth({ data }) { if (data) channel.track({ anon_key: anon, anon_displayName: displayName, id: data.user.id, name: data.user.user_metadata.name }) } setUserChannel(channel) } useEffect(() => { checkAnonymousId() }, []) return [users, currentUser, userChannel] }
已尝试的排查步骤
- 多次重构
useCurrentUser()Hook,并尝试将其放入ContextProvider,问题仍存在。 - 确认其他组件可正常获取更新后的
currentUser,排除数据库层面问题。 - 尝试通过解构赋值创建新对象,未解决问题。
问题分析与修复方案
核心问题
- ContextProvider范围错误:Main组件没有被
CurrentUserContext.Provider包裹,导致它无法感知Context中currentUser的变化。其他能正常更新的组件应该处于该Provider的包裹范围内,所以能收到更新通知,而Main组件作为外层组件,不在监听范围内,自然无法触发重渲染。 - 空值处理不当:
useSupabaseUsers中初始化currentUser为{},当用户未登录时keys.filter(...)可能返回undefined,此时setCurrentUser会把状态设为undefined,与初始状态类型不一致,可能引发状态更新异常。
修复步骤
- 调整ContextProvider包裹范围:确保Main组件被
CurrentUserContext.Provider包裹,示例如下:
// 入口文件或Main的父组件中 function App() { const [users, currentUser, userChannel] = useSupabaseUsers() return ( <CurrentUserContext.Provider value={currentUser}> <Main /> </CurrentUserContext.Provider> ) }
- 处理currentUser空值情况:在
useSupabaseUsers的sync事件逻辑中,避免将currentUser设为undefined,可以保留当前状态或设置默认空对象:
// 修改sync事件处理代码 const matchedUser = keys.filter(x => x.anon_key === anon)[0] setCurrentUser(matchedUser || currentUser) // 无匹配用户时保留当前状态
- 确保状态引用更新:React通过浅比较判断状态是否变化,若
matchedUser是同一个对象引用,即使内部属性变化也不会触发重渲染,可以通过解构赋值生成新对象:
setCurrentUser(matchedUser ? {...matchedUser} : currentUser)
内容的提问来源于Stack Exchange,提问作者Aaron Gayle
相关产品推荐
相关产品推荐

