React组件中状态未更新仍为默认值的原因及解决方法求助
React 回调函数中 useState 状态始终显示初始值的问题
问题描述
在Users组件中,通过useState声明了userData状态,组件内的console.log('user Data:', userData)能正确显示更新后的数值,但在onAddingNewUser回调函数中,console.log('EmaiL:', email, userData)却始终显示默认的空数组。
组件代码如下:
const Users = () => { const { rightSideBarState, setRightSideBarState } = React.useContext(RightSidebarContext); const { setHeaderState } = React.useContext(HeaderContext); const [active, setActive] = React.useState<string | null>(null); const [searchText, setSearchText] = React.useState<string>(''); const [userData, setUserData] = React.useState<User.UserData[]>([]); console.log('user Data:', userData); const onAddingNewUser = async (services: User.SucessfulInvitationSent[], email: string) => { console.log('EmaiL:', email, userData); }; const { refetch } = useQuery([`users`], () => getUsersData(searchText).then((data) => setUserData(data)), { suspense: true, }) ?? []; const onAddUserClicked = () => { setRightSideBarState({ visible: true, rightSidebarDisplay: { headerTitle: 'Add Users', headerCloseAction: () => { setRightSideBarState({ visible: false }); setActive(''); }, content: <RightSidebarCreateUser onAddingNewUser={onAddingNewUser} />, }, }); }; }
问题成因
这是典型的React闭包陷阱:
- 组件初始渲染时,
onAddingNewUser函数被创建,此时它捕获了当前作用域下的userData(空数组)。 - 当
userData通过useQuery更新后,组件会重新渲染并创建新的onAddingNewUser函数,但此时RightSidebarCreateUser组件已经挂载,它接收的还是初始渲染时传递的旧版onAddingNewUser,所以回调里始终访问的是初始的空数组。 - 另外,当前使用
useQuery的方式不规范:直接在查询函数里调用setUserData,不符合useQuery的设计逻辑,容易引发状态同步问题。
解决方法
方法1:使用useRef保存最新状态引用
useRef的.current属性可以随时更新且不会触发组件重渲染,回调函数中可直接访问最新状态值:
const Users = () => { // ...其他状态 const [userData, setUserData] = React.useState<User.UserData[]>([]); // 声明ref保存最新userData const userDataRef = React.useRef<User.UserData[]>([]); // 同步更新ref React.useEffect(() => { userDataRef.current = userData; }, [userData]); const onAddingNewUser = async (services: User.SucessfulInvitationSent[], email: string) => { // 访问ref的current属性获取最新值 console.log('EmaiL:', email, userDataRef.current); }; // ...其他代码 }
方法2:用useCallback包裹回调并添加依赖
通过useCallback让onAddingNewUser在userData变化时重新创建,同时确保打开侧边栏时传递最新的回调函数:
const Users = () => { // ...其他状态 const [userData, setUserData] = React.useState<User.UserData[]>([]); // 用useCallback包裹,依赖userData const onAddingNewUser = React.useCallback(async (services: User.SucessfulInvitationSent[], email: string) => { console.log('EmaiL:', email, userData); }, [userData]); // 同样用useCallback包裹打开侧边栏的函数,依赖最新的onAddingNewUser const onAddUserClicked = React.useCallback(() => { setRightSideBarState({ visible: true, rightSidebarDisplay: { headerTitle: 'Add Users', headerCloseAction: () => { setRightSideBarState({ visible: false }); setActive(''); }, content: <RightSidebarCreateUser onAddingNewUser={onAddingNewUser} />, }, }); }, [setRightSideBarState, setActive, onAddingNewUser]); // 修正useQuery的使用方式:直接返回数据,再同步到state const { data: usersData, refetch } = useQuery( ['users', searchText], // 添加searchText作为查询依赖 () => getUsersData(searchText), { suspense: true } ); React.useEffect(() => { if (usersData) { setUserData(usersData); } }, [usersData]); // ...其他代码 }
方法3:直接使用useQuery返回的data(推荐)
如果不需要额外对userData做本地修改,完全可以跳过useState,直接使用useQuery返回的data,从根源避免状态同步问题:
const Users = () => { // ...其他状态 // 去掉userData的useState,直接用useQuery的data const { data: userData, refetch } = useQuery( ['users', searchText], () => getUsersData(searchText), { suspense: true } ); const onAddingNewUser = React.useCallback(async (services: User.SucessfulInvitationSent[], email: string) => { console.log('EmaiL:', email, userData); }, [userData]); // ...其他代码 }
内容的提问来源于stack exchange,提问作者Alwaysblue
相关产品推荐
相关产品推荐

