#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:
Проблемы
-
Устаревшие вложения в обратных вызовах и неуместный массив зависимостей эффекта
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 });
-
Доступ к состоянию из неправильного цикла рендеринга
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); }
Предложения
Я думаю, что эти изменения должны улучшить ваш код, но могут быть некоторые изменения, связанные с зависимостями и условными тестами.
-
Исправьте устаревшее приложение, объявив обратный вызов вне эффекта. Идея здесь
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); }, []);
-
Исправьте доступ к состоянию, «реагируя» на обновление состояния в отдельном эффекте, вызываемом обновленными переменными состояния как зависимостями.
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 фактически выполняется дважды — сначала с неопределенным именем пользователя, затем (слишком поздно) с определенным.