I have the following code which is working except that it doesn't re-render the page after pressing a button.
I have two different buttons being displayed depending on wether a user is part of a room or not. If a user is part of a room they can click on LEAVE which will execute an API call. I would then like the component to reload and for the button to display JOIN (since they are no longer part of that room).
import JoinedRooms from '../../components/matrix_joined_rooms';
import JoinRoom from '../../components/matrix_function_join_room';
import LeaveRoom from '../../components/matrix_function_leave_room';
import { useEffect, useState } from 'preact/hooks';
const JoinLeaveButton = ({ name, roomId }) => {
const joinedRooms = JoinedRooms();
const [x, setX] = useState(5);
useEffect(() => console.log("re-render because x changed:", x), [x])
const handleXClick = (number) => {
setX(number)
}
if (joinedRooms.includes(name)) {
return <button name={roomId} onClick={() => {
LeaveRoom(roomId);
handleXClick(10);
}
}>LEAVE</button>
} else {
return <button name={roomId} onClick={() => {
JoinRoom(roomId);
handleXClick(20);
}
}>JOIN</button>
}
}
export default JoinLeaveButton;
my JoinRoom and LeaveRoom components are a simple API call which look like this:
const JoinRoom = (roomId) => {
fetch(`https://example.com/_matrix/client/r0/rooms/${roomId}/join`, {
method: "POST",
headers: {
Authorization: `Bearer ${localStorage.getItem("mx_access_token")}`
}
});
}
export default JoinRoom;
The funcionality of the button itself works, the problem is that I have to manually reload the page for it to be displayed correctly.
I have put a dummy state in place which is executed whenever you press the button and it also logs to the console properly. I was under the impression, that changing a state should re-render a component in React (or in this case preact).
Thanks!
In essense: you need to store the state of the joined rooms somewhere and have that state updated everytime a user joins or leaves a room.
I've gone way overboard here, but a custom hook like this makes a lot of sense:
// api calls
const fetchRooms = async userid => { ... }
const joinRoom = async (userId,roomId) => { ... }
const leaveRoom = async (userId,roomId) => { ... }
// custom hook
const useRooms = (userId) => {
const [fetching,setFetching] = useState(true);
const [error,setError] = useState(false);
// joinedRooms state is an empty array when this hook is first used
// it will be updated later using the useEffect hook
// or via the join or leave functions below
const [joinedRooms,setJoinedRooms] = useState([]);
// when the component that uses this hook is mounted
// or the user id changes, update the state
useEffect(() => {
let canceled;
setFetching(true);
(async() => {
try {
const rooms = await fetchRooms(userId);
canceled || setJoinedRooms(rooms);
} catch(err) {
canceled || setError(error);
} finally {
canceled || setFetching(false);
}
})();
return () => canceled = true;
},[userId]);
const leave = async roomId => {
try {
await leaveRoom(userId,roomId)
// alternatively you could fetch all the user rooms again here
setJoinedRooms(joined => joined.filter(r => r !== roomId));
} catch(err) {
// couldn't leave the room - what do you want to do with the state?
}
}
const join = async roomId => {
try {
await joinRoom(userId,roomId);
// alternatively you could fetch all the user rooms again here
setJoinedRooms(joined => [...joined,roomId]);
} catch(err) {
// couldn't join the room - what do you want to do with the state?
}
}
return {
fetching,
error,
joinedRooms,
leave,
join
}
}
In a component you'd use it something like this:
const Rooms = (userId,listOfAllYourRoomIds) => {
const { joinedRooms, fetching, error, join, leave } = useRooms(userId);
// the `join` and `leave` functions are what you'll call
// when a user wants to join or leave a room, the joinedRooms prop will get
// updated according, and everything will "just work"
return listOfAllYourRoomIds.map(roomId => <SomeRoomComponent roomId={roomId}/>)
}