I am very new to react and come across something which seems a little odd to me when setting state with hooks.
Given the state is configured like this:
const [games, setGames] = useState<Array<Game>>([]);
And I update it like this:
const addGame = (gameId: string) => {
setGames([...games, new Game(gameId)])
}
After calling addGame for the second time, I would expect console.log(games.length)
to return two values.
This is not the case and only the latest value is returned. I did some digging and noticed that people were using a lambda to achieve this instead and it does work.
const addGame = (gameId: string) => {
setGames(games => [...games, new Game(gameId)])
}
But why? Can someone explain this, thanks.
Edit by HMR to address the comment
Here are adjustments I made to your code (see comments)
import React, { useState, useEffect, useCallback } from 'react';
import SetName from './SetName';
import { useCookies } from 'react-cookie';
import * as signalR from '@microsoft/signalr';
import { Game } from '../game/domain';
import { Card, Button, Row, Col } from 'antd';
export default function Lobby() {
const [cookie] = useCookies(['swarm']);
const [name, setName] = useState('');
const [games, setGames] = useState([]);
const [hubConnection, setHubConnection] = useState();
//add swarmName variable
const swarmName = cookie.swarm?.name;
useEffect(() => {
if (swarmName) setName(swarmName);
}, [swarmName]); //depends on swarmName
//moved addGame here so it can be a dependency of the effect
// if you don't put this in a useCallback the linter will warn you
const addGame = useCallback((gameId) => {
setGames((games) => [...games, new Game(gameId)]);
}, []);
useEffect(() => {
//defined handler here so you can remove the listener
// in the effect cleanup function
const handler = (gameId) => addGame(gameId);
const createHubConnection = async () => {
const hubConnect = new signalR.HubConnectionBuilder()
.withUrl('/gamehub')
.build();
try {
await hubConnect.start();
console.log('Connection successful!');
} catch (err) {
alert(err);
//rethrow the error for the effect cleanup
return Promise.reject(err);
}
setHubConnection(hubConnect);
await hubConnect.invoke('JoinLobby');
hubConnect.on('addGame', handler);
//returning hubConnect
return hubConnect;
};
//using the return value (is a promise)
const hubConnect = createHubConnection();
//you forgot to return a function here that removes the listener
return () =>
//https://learn.microsoft.com/en-us/javascript/api/@aspnet/signalr/hubconnection?view=signalr-js-latest#off
hubConnect.then((hubConnect) => hubConnect.off('addGame', handler));
}, [addGame]); //when addGame is a useCallback the linter detects missing dependency
let gameRows = games.map((game, index) => (
<div key={index}>
{game.id}
<hr />
</div>
));
return (
<Row>
<Col className="gutter-row" flex="auto"></Col>
<Col className="gutter-row">
<SetName name={name} setName={setName}></SetName>
<Card title="Games" style={{ marginTop: '20px', width: '300px' }}>
<Button
hidden={!cookie.swarm.name}
style={{ marginTop: '7px' }}
type="primary"
onClick={() => hubConnection.invoke('CreateGame')}
block
>
Create Game
</Button>
{gameRows}
</Card>
</Col>
<Col className="gutter-row" flex="auto"></Col>
</Row>
);
}