I followed this this tutorial for signalR with vanilla javascript and all went well. I opened two browsers and both get updated instantly once I send a message from any of them. PROBLEM is located at the bottom.
Then I started another project in VS2017, this time a React template using aspnet core 2.1. Added signalr in Startup.cs
services.AddMvc().SetCompa.....
services.AddSignalR(); // added this
services.AddSpaStaticFiles......
app.UseSpaStaticFiles();
// then added below code
app.UseSignalR(routes =>
{
routes.MapHub<ChatHub>("/chathub");
});
app.UseMvc(.....
Used this chathub class
public class ChatHub : Hub
{
public async Task SendMessage(string user, string message)
{
await Clients.All.SendAsync("ReceiveMessage", user, message);
}
}
and tried to convert the vanilla JS code to this react one
....
this.state = { name: "", message: "", messages: [], hubConnection: null }
...
componentDidMount() {
const connection = new SignalR.HubConnectionBuilder()
.withUrl("/chathub")
.build();
connection.on("ReceiveMessage", (user, message) => this.receiveMessage(user, message))
connection.start().catch(err => console.error(err.toString()));
this.setState({ hubConnection: connection });
}
receiveMessage = (user, message) => {
let msgs = this.state.messages;
const msg = message
.replace("/&/g", "&")
.replace("/</g", "<")
.replace("/>/g", ">");
const encodedMsg = user + " says " + msg;
msgs.push(encodedMsg);
}
sendMessage = async (event) => {
event.preventDefault();
let user = this.state.name;
let message = this.state.message;
let connection = this.state.hubConnection;
await connection
.invoke("SendMessage", user, message)
.catch(err => console.error(err.toString()));
this.setState({ name: "", message: "" });
};
then added the following in render
<p>User: </p><input type="text" id="userInput" value={this.state.name} onChange={event => { this.setState({ name: event.target.value }); }} />
<p>Message: </p><input type="text" id="messageInput" value={this.state.message} onChange={event => { this.setState({ message: event.target.value }); }} />
<input type="button" id="sendButton" onClick={this.sendMessage} value="Send Message"/>
<ul>
{this.state.messages.map(message => (
<li key={message}>{message.toString()}</li>
))}
</ul>
then bound the methods to the class. Issue, other browser does not update instantly when the other browser send message. I need to perform some action which will cause it to rerender, like typing something in the input field. So how do I make it listen to incoming messages and rerender when it receive one? The problem which i think I have is that my client code does not know how to listen to the messages unless it's the one who called explicitly the SendMessage
method
silly me. msgs.push
does not trigger a re-render and just read that it's not good to mutate state. So I just performed a concat on the messages state to return new value, push the new message to the new array then setState
let arr = []
let msgs = this.state.messages.concat(arr);
......
msgs.push(encodedMsg);
this.setState({ messages: msgs });