I'm making a multiplayer game on React and using Colyseus.js as my multiplayer API. The thing is that I need to display all connected players in the lobby:
export default function Lobby() {
const classes = useStyles();
const gameState = useSelector(state => state.roomState);
const history = useHistory();
// Creating array to display all connected players
let playersArray = [];
if(Object.entries(gameState).length !== 0) {
playersArray = Object.values(gameState.players.toJSON());
}
// Check if game has started
useEffect(()=>{
console.log('gamestarted:', gameState.gameStarted);
if(gameState.gameStarted){
history.push('/game')
}
}, [gameState.gameStarted]);
return (
<PageFrame>
<Grid
container
direction="column"
alignItems="center"
className={classes.root}
spacing={2}
>
<Grid item xs={12}>
<Typography className={classes.gameTitle}>
{(gameState != null) ? gameState.gameName : <CircularProgress />}
</Typography>
</Grid>
<Grid item xs={12}>
Waiting for all players to join...
</Grid>
<Grid item xs={12}>
Connected Players: {playersArray.length}
</Grid>
<Grid item xs={12}>
{playersArray.map((data, index) => {
return (
<Chip
key={index}
label={data.party}
className={classes.chip}
style={{"--color": green[500]}}
/>
);
})}
</Grid>
<Grid item xs={12}>
</Grid>
</Grid>
</PageFrame>
);
}
I use useSelector
to get state I received from Colyseus.js and dispatched like this (in another component which has Lobby
as a child in <Route>
):
const joinRoom = (id, partyName, actions) => {
client.joinById(id, {partyName, id}).then(room => {
dispatch({type: GAME_ROOM, payload: room });
dispatch({type: RECONNECT_PARAMS, payload: {roomId: room.id, sessionId: room.sessionId} });
// Redirect to lobby
history.push('/lobby')
{...}
room.onStateChange((state) => {
console.log("the room state has been updated:", state);
// HERE DISPATCHING RECEIVED STATE
dispatch({type: GAME_STATE, payload: state });
});
room.onLeave((code) => {
{...}
});
}
state
from server has property players
with object, which has keys with player names I need to display. My reducer:
const initialState = {
joinDetails:{
gameCode: '',
partyName: ''
},
sessionParams: {
roomId: "",
sessionId: "",
},
room: {},
roomState: {},
scoreChange: null,
totalChange: null,
playerState:{
showResults: false,
questions: [],
totalBudget: 5000,
totalGlobalBudget: 5000,
budget: [],
activeStep: 0,
stepCompleted: [false,false,false,false,false],
decisions: [-1,-1,-1,-1],
}
};
function rootReducer(state = initialState, action) {
switch (action.type) {
case JOIN_FORM:
return {...state, joinDetails: action.payload};
case GAME_ROOM:
return {...state, room: action.payload};
case GAME_STATE:
return {...state, roomState: action.payload};
case RECONNECT_PARAMS:
return {...state, sessionParams: action.payload};
case CHANGE_STEP:
if(action.payload === 'add'){
return {...state, playerState: {...state.playerState, activeStep: state.playerState.activeStep + 1}}}
else{
return {...state, playerState: {...state.playerState, activeStep: state.playerState.activeStep - 1}}}
case COMPLETE_STEP:
return {...state, playerState: {...state.playerState, stepCompleted: action.payload}};
case SET_BUDGET:
return {...state, playerState: {...state.playerState, budget: action.payload}};
case SET_QUESTIONS:
return {...state, playerState: {...state.playerState, questions: action.payload}};
case SET_TOTAL_BUDGET:
if(action.payload.add !== undefined){
return {...state, playerState: {...state.playerState, totalBudget: state.playerState.totalBudget + action.payload.add}}
}
else{
return {...state, playerState: {...state.playerState, totalBudget: action.payload}};
}
case SET_GLOBAL_BUDGET:
if(action.payload.add !== undefined){
return {...state, playerState: {...state.playerState, totalGlobalBudget: state.playerState.totalGlobalBudget + action.payload.add}}
}
else{
return {...state, playerState: {...state.playerState, totalGlobalBudget: action.payload}}
}
case SET_DECISIONS:
return {...state, playerState: {...state.playerState, decisions: action.payload}};
case SET_CHANGES:
return {...state, scoreChange: action.payload.change, totalChange: action.payload.total};
case SHOW_RESULTS:
return {...state, playerState: {...state.playerState, showResults: action.payload}};
case CLEAR_STATE:
return {...state, playerState: {}};
default:
return state;
}
}
export default rootReducer;
The problem is that player list does not update after new players join. I can confirm that:
useSelector
which works, because each player sees the list of players who joined before him.I tried looking for mutated state, but it looks to me I everything is good there. It's my first redux project, so maybe I'm missing something. One suspicious thing I notice in redux devtools that sometimes(,) the latest state propagates trough all previous states in devtools view. eg, "Diff" tab shows that all same actions resulted in no changes except the first one has changed state to final form: for example:
Actual changes:
Shown changes (but they became like this only after third time):
Not sure if this normal or causes my issue.
If you need aditional code let me know.
Thanks.
EDIT: Changed some names, as per advice in comments, issue still perisits.
EDIT 2: The problem seems directly related to useSelector
: currently I have
gameState = useSelector(state => state.roomState)
but if I select the whole state, everything works, component rerenders.
gameState = useSelector(state => state)
this is far from ideal: now I need to call gameState.roomState.something
instead of gameState.something
everywhere. How it can be that useSelector
doesn't recognize the change of particular part of state and only recognize the change of whole state?
So my problem was here:
room.onStateChange((state) => {
console.log("the room state has been updated:", state);
// HERE DISPATCHING RECEIVED STATE
dispatch({type: GAME_STATE, payload: state });
I was putting state
to payload without destructuring/copying it to new object, thus actually only passing a reference. And of course, when changed it also changed all other references and there was nothing to compare to. The correct way is destructuring received state
while passing it to action:
room.onStateChange((state) => {
console.log("the room state has been updated:", state);
// HERE DISPATCHING RECEIVED STATE
dispatch({type: GAME_STATE, payload: {...state} })
After your second edit, I had a better look at your action to find why state.roomState hasn't change for trigger the useSelector, and can't believe it, the exact same question I answered two weeks ago - How do I dispatch multiple actions in react-native using hooks?
you can attach more than one payload to dispatch. Change joinRoom action to one dispatch hold both "GAME_ROOM" and "RECONNECT_PARAMS"
client.joinById(id, {partyName, id}).then(room => {
dispatch({
type: 'GAME_ROOM',
room,
params: {roomId: room.id, sessionId: room.sessionId}
});
..........
and then in the reducer combine them to one case listener
case 'GAME_ROOM':
return {...state, room: action.room, sessionParams: action.params }
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.