Почему состояние не обновляется

#reactjs #cookies #use-effect #use-state

#reactjs #файлы cookie #использовать-эффект #использовать-состояние

Вопрос:

Когда новый пользователь посещает страницу, он может ввести имя пользователя, которое хранится в состоянии и в файле 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 не определено. Я знаю, что оно установлено, потому что я а) вижу файл cookie, б) вижу имя пользователя, загруженное из файла cookie, в заголовке страницы.

Итак, почему здесь оно отображается как неопределенное? То же самое относится и к useState, который показывает начальное значение ( null ), а не фактическое значение…

РЕДАКТИРОВАТЬ: полный код здесь — https://github.com/faire2/loreHunters/blob/master/src/components/loginPage/LoginPage.js

Код находится в режиме реального времени в arnak-dev.herokuapp.com .

введите описание изображения здесь

Комментарии:

1. «Когда новый пользователь посещает страницу, он может ввести имя пользователя» — что, если ваш посетитель — женщина?

2. Если вы используете эффект для простого внесения изменений в журнал cookies , видите ли вы, что он обновляется в вашем компоненте? Можете ли вы включить более полный пример компонента? Что все это влияет на socket выполнение? Я подозреваю, что вы закрыли свое cookies значение при startGame обратном вызове.

3. cookies.username еще не будет определено, когда вы useState(cookies.username ? cookies.username : null); и ваш useEffect запускаете только один раз при монтировании компонента. Вы не имеете никакого эффекта для обновления каких-либо сведений о сокете при cookies обновлении состояния.

4.Верно, я думаю, у вас есть несколько проблем. Во-первых, да, похоже, что вы закрыли свое начальное cookies состояние в обработчиках socket событий, поскольку useEffect оно имеет пустой массив зависимостей. Во-вторых, в нескольких ваших функциях, которые вы вызываете setCookie , но затем через одну или две строки выдается текущее cookie.username значение (а не то, которое только что было поставлено в очередь для следующего цикла рендеринга). В-третьих, (ну, я думаю, связано с первым) ваш эффект не возвращает функцию очистки для (А) отключения / закрытия любых открытых сокетов и (Б) повторного включения обновленного cookies значения состояния в обработчиках событий.

5. Либо сокет должен оставаться открытым, пока вы не перейдете к новому маршруту, либо, как минимум, отмените регистрацию обработчиков событий, добавленных компонентом страницы входа. Вероятно, вам нужен эффект монтирования с возвращаемой функцией очистки. Я думаю, чтобы решить вашу основную проблему со cookies значением, вам нужно определить обратные вызовы сокетов, которые зависят от cookies внешнего по отношению к эффекту перехвата. Если вы хотите, я могу привести пример в качестве ответа.

Ответ №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 amp;amp; !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]);
      

Комментарии:

1. Я внедрил оба предложенных вами изменения, но по-прежнему возникает та же ошибка. Я перенес ваши изменения в репозиторий, чтобы вы могли их видеть, вы также можете проверить, что происходит по адресу arnak-dev.herokuapp.com/game . Что я понимаю еще меньше, так это то, что if (cookies.username amp;amp; !shakedHand) часть useEffect регистрирует новый файл cookie сразу. Я ошеломлен.

2. @Faire Я немного поиграл с вашим приложением на heroku, похоже, оно работает вплоть до открытия игры, но я спотыкаюсь в темноте. Я вижу formerUsername , что состояние обновляется для моего имени пользователя, установлены файлы cookie. Я думаю, что я вижу shakedHand , что состояние не обновляется. Можете ли вы предоставить шаги воспроизведения?

3. Воспроизведение составляет 100%: 1. cookie не существует. 2. создайте новое имя пользователя. 3. без обновления создайте новую игру и запустите ее. Произойдет сбой, потому что он не сможет определить пользователя.

4. Но кажется, что передачи всегда вызывают эффект useState, который был создан при инициализации страницы. Я изменил код так, чтобы я явно передавал username в функцию startGame с протоколированием. Я вижу, что имя пользователя правильно передано функции (отладчик показывает, что новое имя пользователя известно useEffect), но как только оно достигает функции startGame, имя пользователя снова не определено.

5. Ведение журнала показывает, что функция startGame фактически выполняется дважды — сначала с неопределенным именем пользователя, затем (слишком поздно) с определенным.