【问题标题】:Why does the state not update为什么状态不更新
【发布时间】:2021-02-17 14:52:45
【问题描述】:

当新用户访问页面时,他们可以填写一个用户名,该用户名存储在 state 和 cookie 中。我稍后想访问该 cookie 和状态以在游戏会话中识别用户名,但是它们此时都是空的,即使我知道它们已被设置(日志记录会看到值,用户名正确显示在标题中)。为什么会这样?

const [cookies, setCookie] = useCookies(["username"]);
const [formerUsername, setFormerUsername] = useState(cookies.username ? cookies.username : null);
...

function handleUsernameChange(username) {
        setCookie("username", username, {path: "/", expires: expirationDate});
        setFormerUsername(username);
        ...
    }
    
useEffect(() => {
        ...
socket.on(TRANSMISSIONS.startGame, data => {
            let playerIndex = data.room.players.indexOf(cookies.username);
            history.push({pathname: "/game", data: {username: cookies.username, room: data.room, playerIndex: playerIndex,
                    }});
        });

播放器索引为 -1,因为 cookies.username 未定义。我知道它已设置,因为我 a) 看到 cookie,b) 在页面标题中看到从 cookie 加载的用户名。

那么为什么这里显示为未定义呢? useState 也是如此,它显示的是初始值 (null),而不是实际值...

编辑:完整代码在这里 - https://github.com/faire2/loreHunters/blob/master/src/components/loginPage/LoginPage.js

代码位于 arnak-dev.herokuapp.com。

【问题讨论】:

  • “当一个新用户访问页面时,他可以填写一个用户名” - 如果你的访问者是女性怎么办?
  • 如果您使用效果来简单地将更改记录到cookies,您是否看到它在您的组件中更新?你能包括一个更完整的组件示例吗? socket 的作用是什么all?我怀疑您已经在 startGame 回调中关闭了您的 cookies 值。
  • cookies.username 在您 useState(cookies.username ? cookies.username : null); 时尚未定义,并且您的 useEffect 在组件挂载时仅运行一次。当cookies 状态更新时,您无法更新任何套接字详细信息。
  • 好吧,我认为你有一些问题。首先,是的,看起来您已经在 socket 事件处理程序中关闭了初始 cookies 状态,因为 useEffect 有一个空的依赖数组。其次,在您调用的几个函数中 setCookie 但随后一两行发出 current cookie.username 值(不是刚刚排队等待 下一个渲染周期)。第三,(我猜与第一个相关)你的效果不会返回清理函数来(A)断开/关闭任何打开的套接字和(B)重新封闭更新的cookies状态值事件处理程序。
  • 在您导航到新路由之前,套接字需要保持打开状态,或者至少取消注册登录页面组件添加的事件处理程序。您可能希望通过返回的清理功能获得安装效果。我认为要解决 cookies 值的主要问题,您需要定义 depend 在效果挂钩外部的cookies 的套接字回调。如果你愿意,我可以提供一个例子作为答案。

标签: reactjs cookies use-effect use-state


【解决方案1】:

问题

  1. 回调中的陈旧外壳,以及放错位置的效果依赖数组

    useEffect(() => {
      // extend cookie if it exists
      ...
    
      socket.on(TRANSMISSIONS.startGame, data => {
        let playerIndex = data.room.players.indexOf(cookies.username);
        history.push({
          pathname: "/game",
          data: {
            username: cookies.username, // <-- value when effect ran
            room: data.room,
            playerIndex: playerIndex,
          },
        });
      });
    
      ...
    
      socket.on(TRANSMISSIONS.currentUsersAndData, data => {
        console.log("received actual room and users data");
        if (!shakedHand) { // <-- stale
            setShakedHand(true)
        }
        setUsers(data.users);
        setRooms(data.rooms);
        setRoomIsFull(false);
      }, []) // <-- dependency array
    });
    
  2. 从错误的渲染周期访问状态

    function handleUsernameChange(username) {
      setCookie("username", username, {path: "/", expires: expirationDate});
      setFormerUsername(username);
    
      if (!formerUsername) { // <-- current formerUsername state
        socket.emit(
          TRANSMISSIONS.handShake,
          cookies.username, // <-- current cookies state
        );
      } else {
        socket.emit(
          TRANSMISSIONS.usernameChanged,
          {
            formerUsername: formerUsername, // <-- current formerUsername state
            newUsername: username, // <-- current username state
          },
        );
      }
    
      setShowCreateUsername(false);
    }
    

建议

我认为这些更改应该让您的代码更好,但可能需要对依赖项和条件测试进行一些调整。

  1. 通过在效果外声明回调来修复陈旧的外壳。这里的想法是 onStartGameHandler 在每个渲染周期中定义并包含当前状态。

    const onStartGameHandler = data => {
      const playerIndex = data.room.players.indexOf(cookies.username);
      history.push({
        pathname: "/game",
        data: {
          username: cookies.username,
          room: data.room,
          playerIndex: playerIndex,
        },
      });
    };
    
    const onUsersAndDataHandler = data => {
      console.log("received actual room and users data");
      if (!shakedHand) {
        setShakedHand(true)
      }
      setUsers(data.users);
      setRooms(data.rooms);
      setRoomIsFull(false);
    }
    
    useEffect(() => {
      // extend cookie if it exists
      if (cookies.username && !shakedHand) { ...
    
      ...
    
      socket.on(TRANSMISSIONS.startGame, onStartGameHandler);
    
      ...
    
      socket.on(TRANSMISSIONS.currentUsersAndData, onUsersAndDataHandler);
    
      // return clean up function
      return () => socket.off(TRANSMISSIONS.startGame);
    }, []);
    
  2. 通过在单独的效果中对状态更新“做出反应”来修复状态访问,将更新的状态变量作为依赖项触发。

    function handleUsernameChange(username) {
      setCookie("username", username, {path: "/", expires: expirationDate});
      setFormerUsername(username);
      setShowCreateUsername(false);
    }
    
    ...
    
    useEffect(() => {
      if (!formerUsername) {
        socket.emit(TRANSMISSIONS.handShake, cookies.username);
      } else {
        socket.emit(
          TRANSMISSIONS.usernameChanged,
          {
            formerUsername: formerUsername,
            newUsername: username,
          },
        );
      }
    }, [cookies, formerUsername, username]);
    

【讨论】:

  • 我已经实现了您提供的两项更改,但仍然出现相同的错误。我已将您的更改推送到 repo,以便您可以看到它们,您也可以在 arnak-dev.herokuapp.com/game 上查看实时内容。我更不了解的是,useEffect 的if (cookies.username &amp;&amp; !shakedHand) 部分会立即注册新的cookie。我傻了。
  • @Faire 我在 heroku 上玩过你的应用程序,它似乎可以工作直到打开游戏,但 TBH 我在黑暗中跌跌撞撞。我看到formerUsername 状态更新到我的用户名、cookie 集。我想我看到shakedHand 状态没有更新。能否提供复现步骤?
  • 复制率100%: 1. 不存在cookie。 2.创建新用户名。 3.不刷新创建一个新游戏并启动它。它会崩溃,因为它无法确定用户。
  • 但似乎传输总是触发页面初始化时创建的 useState 效果。我更改了代码,以便将用户名显式传递给带有日志记录的 startGame 函数。我看到用户名已正确传递给函数(调试器显示新用户名对于 useEffect 是已知的),但一旦到达 startGame 函数,用户名就再次未定义。
  • 记录显示 startGame 函数实际上运行了两次 - 第一次使用未定义的用户名,然后(为时已晚)使用已定义的用户名。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2021-08-17
  • 1970-01-01
  • 2021-08-04
  • 2022-11-22
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多