Reputation: 549
I am building a chat app and trying to match the id params to render each one on click.I have a RoomList component that maps over the rooms via an endpoint /rooms
I then have them linked to their corresponding ID. THe main components are Chatroom.js and RoomList is just the nav
import moment from 'moment';
import './App.scss';
import UserInfo from './components/UserInfo';
import RoomList from './components/RoomList';
import Chatroom from './components/Chatroom';
import SendMessage from './components/SendMessage';
import { Column, Row } from "simple-flexbox";
import { Route, Link, Switch } from 'react-router-dom'
function App() {
const timestamp = Date.now();
const timeFormatted = moment(timestamp).format('hh:mm');
const [username, setUsername] = useState('');
const [loggedin, setLoggedin] = useState(false);
const [rooms, setRooms] = useState([]);
const [roomId, setRoomId] = useState(0);
const handleSubmit = async e => {
e.preventDefault();
setUsername(username)
setLoggedin(true)
};
useEffect(() => {
let apiUrl= `http://localhost:8080/api/rooms/`;
const makeApiCall = async() => {
const res = await fetch(apiUrl);
const data = await res.json();
setRooms(data);
};
makeApiCall();
}, [])
const handleSend = (message) => {
const formattedMessage = { name: username, message, isMine: true};
}
return (
<div className="App">
<Route
path="/"
render={(routerProps) => (
(loggedin !== false) ?
<Row>
<Column>
{/*<Chatroom roomId={roomId} messages={messages} isMine={isMine}/>*/}
</Column>
</Row>
:
<form onSubmit={handleSubmit}>
<label htmlFor="username">Username: </label>
<input
type="text"
value={username}
placeholder="enter a username"
onChange={({ target }) => setUsername(target.value)}
/>
<button type="submit">Login</button>
</form>
)}
/>
<Switch>
<Route
exact
path="/:id"
render={(routerProps) => (
<Row>
<Column>
<UserInfo username={username} time={timeFormatted}/>
<RoomList rooms={rooms}/>
</Column>
<Column>
<Chatroom {...routerProps} roomId={roomId}/>
<SendMessage onSend={handleSend}/>
</Column>
</Row>
)}
/>
</Switch>
</div>
);
}
export default App;
RoomList.js
import { Row } from "simple-flexbox";
const RoomList = (props) => {
return (
<div className="RoomList">
<Row wrap="false">
{
props.rooms.map((room, index) => {
return (
<Link to={`/${room.id}`} key={index}>{room.id} {room.name}</Link>
)
})
}
</Row>
</div>
)
}
export default RoomList;
Chatroom.js this is the main component that should render based on the ID
import Message from './Message';
import { Link } from 'react-router-dom'
const Chatroom = (props) => {
const [roomId, setRoomId] = useState(0);
const [name, setName] = useState('Roomname')
const [messages, setMessages] = useState([]);
useEffect(() => {
let apiUrl= `http://localhost:8080/api/rooms/`;
const id = props.match.params.id;
const url = `${apiUrl}${id}`;
const makeApiCall = async () => {
const res = await fetch(url);
const data = await res.json();
setRoomId(data.id);
setUsers(data.users)
setName(data.name)
};
makeApiCall();
}, []);
useEffect(() => {
const id = props.match.params.id;
const url = `http://localhost:8080/api/rooms/${id}/messages`;
const makeApiCall = async() => {
const res = await fetch(url);
const data = await res.json();
setMessages(data);
};
makeApiCall();
}, [])
return (
<div className="Chatroom">
{name}
</div>
)
}
export default Chatroom;```
when I click on the links I want the change to refresh the new content but it wont? any ideas why ? thank you in advance!
Upvotes: 0
Views: 50
Reputation: 476
Notice that your functional component named App does not have any dependencies and that is fine since data should just be fetched once, on mount. However, on ChatRoom we want a new fetch everytime that roomId changes.
First thing we could do here is adding props.match.params.id directly into our initial state.
const [roomId, setRoomId] = useState(props.match.params.id); // set up your initial room id here.
Next we can add an effect that checks if roomId needs updating whenever props change. Like this:
useEffect(()=>{
if(roomId !== props.match.params.id) {
setRoomId(props.match.params.id)
}
}, [props])
Now we use roomId as our state for the api calls and add it in the brackets (making react aware that whenever roomId changes, it should run our effect again).
useEffect(() => {
let url = "http://localhost:8080/api/rooms/" + roomId; // add room id here
const makeApiCall = async () => {
const res = await fetch(url);
const data = await res.json();
setUsers(data.users)
setName(data.name)
};
makeApiCall();
}, [roomId]); // very important to add room id to your dependencies as well here.
useEffect(() => {
const url = `http://localhost:8080/api/rooms/${roomId}/messages`; // add room id here as well
const makeApiCall = async() => {
const res = await fetch(url);
const data = await res.json();
setMessages(data);
};
makeApiCall();
}, [roomId]) // very important to add room id to your dependencies as well here.
I believe that it should work. But let me build my answer upon this: When mounted, meaning that this is the first time that the ChatRoom is rendered, it will go through your useEffect and fetch data using roomId as the initial state that we setup as props.match.params.id.
Without dependencies, he is done and would never fetch again. It would do it once and that's it. However, by adding the dependency, we advise react that it would watch out for roomId changes and if they do, it should trigger the function again. It is VERY IMPORTANT that every variable inside your useEffect is added to your brackets. There is eslint for it and it is very useful. Have a look at this post. It helped me a lot.
https://overreacted.io/a-complete-guide-to-useeffect/
Let me know if it works and ask me if there is still doubts. =)
Upvotes: 1