Reputation: 66
I'm doing a chat service.
Everything is working fine, expect the following functionality :
To do so, I tried the following reducer :
case NEW_CHAT_MESSAGE:
const setRoomFirst = (array, roomId) => {
const chatIndex = array.findIndex((arr) => arr._id === roomId);
array.push(...array.splice(0, chatIndex));
return { arr: array, hasBeenUpdated: chatIndex !== -1 };
};
const rooms = state.rooms;
// move room to first position
const updatedRooms = setRoomFirst(rooms, action.payload.message.chatRoom);
if (updatedRooms.hasBeenUpdated) {
// will always be the first room
updatedRooms.arr[0].messages.push(action.payload.message);
}
return { ...state, rooms: updatedRooms.arr };
default:
return state;
}
The component that will manage the state of the chat :
function Chat() {
const classes = useStyle();
const dispatch = useDispatch();
const callback = useCallback(
(rooms) => {
console.log("dispatch");
dispatch(newChatMessage(rooms));
},
[dispatch]
);
const { roomId } = useParams();
const rooms = useSelector(ChatRoomsSelector);
const [selectedRoom, setSelectedRoom] = useState(
roomId || (rooms.length ? rooms[0]._id : null)
);
const oldRoomsValue = usePrevious(rooms);
useEffect(() => {
console.log("rooms has changed", rooms);
if (!selectedRoom) {
setSelectedRoom(roomId || (rooms.length ? rooms[0]._id : null));
}
if (oldRoomsValue !== rooms) {
console.log("a change in rooms has been detected");
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [rooms]);
const onSelect = useCallback((roomId) => {
setSelectedRoom(roomId);
}, []);
const current = rooms.filter((room) => room._id === selectedRoom)[0];
return (
<Container>
<Typography variant="h1" className={classes.title}>
Messagerie
</Typography>
<Grid container spacing={2}>
<Grid item xs={4}>
<ChatListContainer
rooms={rooms}
roomSelected={selectedRoom}
onSelect={onSelect}
/>
</Grid>
<Grid item xs={8}>
{current && <ChatContentContainer room={current} />}
</Grid>
</Grid>
</Container>
);
}
export default Chat;
Thank you in advance for your help! :)
Upvotes: 1
Views: 388
Reputation: 39320
You are mutating the redux state and you did not indicate you are using immer so that's something you should not do:
case NEW_CHAT_MESSAGE:
const {
payload: { message },
} = action;
//find or create room
const room = state.rooms.find(
(room) => room._id === message.chatRoom
) || { _id: message.chatRoom, messages: [] };
//add message to room
const roomWithMessage = {
...room,
messages: [...room.messages, message],
};
return {
...state,
//set room as first in rooms
rooms: [
//room with message is the first
roomWithMessage,
//other rooms without the room with message
...state.rooms.filter(
(room) => room._id !== roomWithMessage._id
),
],
};
Your effect has missing dependencies, this can cause some problem when you want the effect to run and I found some other inconsistancies (see comments in code):
function Chat() {
const dispatch = useDispatch();
// your code does not show how you need to use this
// if this is to dispatch a new chatmessage then the
// paremeter passed would not be rooms but message {chatRoom:roomid, ...other}
// const callback = useCallback(
// (rooms) => {
// console.log('dispatch');
// dispatch(newChatMessage(rooms));
// },
// [dispatch]
// );
const { roomId } = useParams();
const rooms = useSelector(ChatRoomsSelector);
const [selectedRoom, setSelectedRoom] = useState(
//use optional chaining for cleaner syntax:
//https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Optional_chaining
roomId || rooms?.[0]?._id
);
//use Array.prototype.find if you want to find one item
const current = rooms.find(
(room) => room._id === selectedRoom
);
useEffect(() => {
//a console.log does not show what you want to do here
// the only thing you actually did is change selectedRoom
// when current room changes and no selectedRoom was provided
setSelectedRoom(selectedRoom=>selectedRoom || current?._id);
}, [current]);
const onSelect = useCallback((roomId) => {
setSelectedRoom(roomId);
}, []);
return 'jsx';
}
Upvotes: 1
Reputation: 66
Thank you so much HMR for your answer ! I would give you a hug if I had a chance. ^^
Just one thing to correct : I needed to enumerate the result of filter in the reducer to make it work.
return {
...state,
// set room as first in rooms
rooms: [
// room with message is the first
roomWithMessage,
// other rooms without the room with message
...state.rooms.filter((room) => room._id !== roomWithMessage._id),
],
};
Upvotes: 1