Is there any way that we can use react and SignalR together.
I tried out some examples but I was not success.
componentDidMount = () => {
const nick = window.prompt('Your name:', 'John');
//const hubConnection = new HubConnection('http://localhost:5000/chat');
//Said deprecated
const hubConnection = new HubConnectionBuilder().withUrl('http://localhost:5000/chat').build();
this.setState({ hubConnection, nick }, () => {
this.state.hubConnection
.start()
.then(() => console.log('Connection started!'))
.catch(err => console.log('Error while establishing connection :('));
this.state.hubConnection.on('sendToAll', (nick, receivedMessage) => {
const text = `${nick}: ${receivedMessage}`;
const messages = this.state.messages.concat([text]);
this.setState({ messages });
});
});
}
This is an example, where the HubConnection is provided. But when trying to set it to the state hubConnection variable, I'm saying the 'hubConnection variable does not exist on type ReadOnly'
Why I'm receiving this error?
Can someone please help?
Per the Redux FAQ, the right place for websockets and other similar connections is in Redux middleware. Some examples online describe how to add SignalR in a component which is a bad practice. Because if your component gets unmounted then signalR instance has to be recreated.
It is easy to create a middleware, and you can use authentication for establishing connection, and you can dispatch action creators based on different messages emitted from SignalR.
This is my custom middleware that establishes the connection, and registers the handlers. Please note that I only would like to receive data, and not interested in sending data. I use REST APIs to send data to server.
import {
JsonHubProtocol,
HttpTransportType,
HubConnectionBuilder,
LogLevel
} from '#aspnet/signalr'; // version 1.0.4
// action for user authentication and receiving the access_token
import { USER_SIGNED_IN } from '../actions/auth';
const onNotifReceived = res => {
console.log('****** NOTIFICATION ******', res);
};
const startSignalRConnection = connection => connection.start()
.then(() => console.info('SignalR Connected'))
.catch(err => console.error('SignalR Connection Error: ', err));
const signalRMiddleware = ({ getState }) => next => async (action) => {
// register signalR after the user logged in
if (action.type === USER_SIGNED_IN) {
const urlRoot = (window.appConfig || {}).URL_ROOT;
const connectionHub = `${urlRoot}/api/service/hub`;
const protocol = new JsonHubProtocol();
// let transport to fall back to to LongPolling if it needs to
const transport = HttpTransportType.WebSockets | HttpTransportType.LongPolling;
const options = {
transport,
logMessageContent: true,
logger: LogLevel.Trace,
accessTokenFactory: () => action.user.access_token
};
// create the connection instance
const connection = new HubConnectionBuilder()
.withUrl(connectionHub, options)
.withHubProtocol(protocol)
.build();
// event handlers, you can use these to dispatch actions to update your Redux store
connection.on('OperationProgress', onNotifReceived);
connection.on('UploadProgress', onNotifReceived);
connection.on('DownloadProgress', onNotifReceived);
// re-establish the connection if connection dropped
connection.onclose(() => setTimeout(startSignalRConnection(connection), 5000));
startSignalRConnection(connection);
}
return next(action);
};
export default signalRMiddleware;
And inside my store.js file
import signalRMiddleware from '../middlewares/signalRMiddleware';
...
createStore(rootReducer, {}, composeEnhancers(applyMiddleware(signalRMiddleware)));
Related
I am trying to store MQTT client in #redux-toolkit reducer but getting an error 'A non-serializable value was detected
so is there any better approach then storing the client in reducer because I need MQTT client in onCacheEntryAdded to update my cache
const client = MQTT(socketUrl, options)
client.stream.on("error", (err) => {
toast.error(`Connection to ${socketUrl} failed`)
client.end()
return
})
dispatch(updateClient(client))
here is my onCacheEntryAdded function
async onCacheEntryAdded(
arg,
{ updateCachedData, cacheDataLoaded, cacheEntryRemoved, getState }
) {
try {
const state = getState()
const client = state.inbox.client
await cacheDataLoaded
client.on("message", (topic, data) => {
const message = JSON.parse(data.toString())
updateCachedData((draft) => {
if (!message.payload) return
draft.messages.unshift(message.payload)
})
})
} catch (err) {}
await cacheEntryRemoved
},
It's a general best practice of Redux that your state should not contain any non-serializable values, just raw data, so storing a client in your state is not advised.
I'm not too familiar with MQTT so I cannot guarantee that this will work. It seems like you could store the socketUrl and options variables in your Redux state. Then construct the MQTT instance inside of the onCacheEntryAdded callback.
async onCacheEntryAdded(
arg,
{ updateCachedData, cacheDataLoaded, cacheEntryRemoved, getState }
) {
try {
const state = getState()
const { socketUrl, options } = state.inbox.clientConfig
const client = MQTT(socketUrl, options)
await cacheDataLoaded
client.on("message", (topic, data) => {
const message = JSON.parse(data.toString())
updateCachedData((draft) => {
if (!message.payload) return
draft.messages.unshift(message.payload)
})
})
} catch (err) {}
await cacheEntryRemoved
client.end()
},
This is similar to the WebSocket streaming update examples in the docs, which call const ws = new WebSocket('ws://localhost:8080') inside of onCacheEntryAdded.
I have a chat app I made using React for the frontend, DRF for the backend and I also have a node.js server within the React app for socket.io The issue is that the chat doesn't work basically (it works fine locally). When a message is sent it's not emitted and only shows up when I refresh since it's then pulled from the DB instead. I have gone through many threads on here for this issue but can't figure out what I'm doing wrong.
My server:
const server = require("http").createServer();
const io = require("socket.io")(server, {
cors: {
origin: "*",
},
});
const PORT = process.env.PORT || 5000;
const NEW_CHAT_MESSAGE_EVENT = "newChatMessage";
io.on("connection", (socket) => {
console.log('Client connected')
// Join a conversation.
const {roomId} = socket.handshake.query;
socket.join(roomId);
// Listen for new messages
socket.on(NEW_CHAT_MESSAGE_EVENT, (data) => {
io.in(roomId).emit(NEW_CHAT_MESSAGE_EVENT, data);
});
// Leave the room if the user closes the socket
socket.on("disconnect", () => {
socket.leave(roomId);
});
});
server.listen(PORT, (error) => {
if (error) throw error;
console.log(`Listening on port ${PORT}`);
});
Hook I made for the frontend:
const NEW_CHAT_MESSAGE_EVENT = "newChatMessage"; // Name of the event
const SOCKET_SERVER_URL = `https://<my-react-frontend>.herokuapp.com`;
export const useChat = () => {
const socketRef = useRef();
const {messages, setMessages, activeConvo, headerConvo, reloadSideBar, setReloadSideBar} = useActiveConvo()
const roomId = activeConvo
useEffect(() => {
console.log('useChat useEffect ran')
// Creates a WebSocket connection
socketRef.current = socketIOClient(SOCKET_SERVER_URL, {
query: {roomId},
});
// Listens for incoming messages
socketRef.current.on(NEW_CHAT_MESSAGE_EVENT, (message) => {
const incomingMessage = {
message: message.body,
created_by: localStorage.getItem('currentUserID'),
};
console.log('messages set in useChat useFfect')
setMessages((messages) => [...messages, incomingMessage]);
});
// Destroys the socket reference
// when the connection is closed
return () => {
socketRef.current.disconnect();
};
}, [roomId]);
// Sends a message to the server that
// forwards it to all users in the same room
const sendMessage = (messageBody) => {
socketRef.current.emit(NEW_CHAT_MESSAGE_EVENT, {
body: messageBody,
senderId: socketRef.current.id,
});
const fetchContents = {
message: messageBody,
created_by: localStorage.getItem('currentUserID'),
convo_id: activeConvo ? activeConvo : headerConvo
}
fetch(`https://<my-drf-backend>.herokuapp.com/api/save-message/`, authRequestOptions(('POST'), fetchContents))
.then(response => response.json())
.then(setReloadSideBar(reloadSideBar + 1))
.catch(error => console.log(error))
};
return {messages, sendMessage};
};
The issue in most of the threads appeared to be either still using the localhost url on the frontend or not using process.env.PORT in the server but it's still busted after fixing that. I also saw someone mention in another thread that the folder structure was the issue so I tried having the server file in the root of the react app and having it in it's own folder under "src", no dice.
In case anyone faces this same issue, I solved it by putting the server in a separate app on heroku
I am working on adding websockets to my Node/React app to automatically reflect changes to all the clients. So I have a websockets helper module that has onclose, onopen and onmessage events as well as a readyState function. So my component that needs the updated websocket values makes a call to that module and gets back data. That data variable is coming over empty, but when I console it out in the onmessage event in the module itself, it has all the info I want.
So here is how I call the websocket module in my component:
const onConnected = (socket) => {
socket.send(
JSON.stringify({
eventType: 'clientCount'
})
);
};
const { socket, readyState, reconnecting, data } = useWebsocket({
url: wsURL + ':' + process.env.REACT_APP_WS_PORT,
onConnected
});
I have a useEffect that should spit out the updated values from data:
useEffect(() => {
console.log('data changed!!!!');
console.log({ data });
console.log({ socket });
console.log({ readyState });
if (data) {
setNumberClients(data.numberClients);
setNumberIpads(data.numberIpads);
}
}, [data, readyState]);
And finally here is my websockets module itself:
import { useState, useEffect, useRef } from 'react';
export default function useWebsocket({ url, onConnected }) {
const [data, setData] = useState([]);
const [reconnecting, setReconnecting] = useState(false);
const socket = useRef(null);
useEffect(() => {
console.log('running socket hook');
socket.current = new WebSocket(url);
socket.current.onopen = () => {
console.log('connected');
onConnected(socket.current);
};
socket.current.onclose = () => {
console.log('closed');
if (socket.current) {
if (reconnecting) return;
setReconnecting(true);
setTimeout(() => setReconnecting(false), 2000);
socket.current.close();
socket.current = undefined;
}
};
socket.current.onmessage = (e) => {
const wsData = JSON.parse(e.data);
console.log('message received ', wsData);
//setData((prev) => [...prev, wsData]);
setData(wsData);
};
return () => {
socket.current.close();
socket.current = null;
};
}, [reconnecting, url]);
const readyState = () => {
if (socket.current) {
switch (socket.current.readyState) {
case 0:
return 'CONNECTING';
case 1:
return 'OPEN';
case 2:
return 'CLOSING';
case 3:
return 'CLOSED';
default:
return;
}
} else {
return null;
}
};
return {
socket: socket.current,
readyState: readyState(),
reconnecting,
data
};
}
So data is always an empty array when I console it out in my component. But in the websockets module, it(wsData) has the info I need.
One More Thing: I am following the tutorial here: https://github.com/devmentorlive/websocket-direct-chat-client/tree/2-as-a-hook/src/chat
Update 2: I have a github repo showing the exact issue here: https://github.com/dmikester1/websockets-test
Use Server and Start scripts to kick things off.
It's not a problem on front-end side.
I think you have used useWebsocket hook twice - once in the SchedulePage and again in the ClientCountContainer so that you can check if 2 clients are displayed.
The problem is that the socket client you defined in ClientCountContainer component is not receiving the message from the server.
After looking at the websocket server, I noticed that it broadcasts messages to websocket clients that are saved in clients array. Not all the websocket client is saved in this array, but only the client which sends {eventType: 'connect'} message to the server is saved in that array.
The websocket client you created using useWebsocket hook in SchedulePage component is saved in clients array on websocket server, because it first sends {eventType: 'connect'} message to the server. But the websocket client you created in ClientCountContainer is not saved in that array.
Therefore, the messages containing clientCount information is not sent to the second websocket client which is defined in ClientCountContainer.
To get rid of this from happening, you can simply add this code snippet in the ClientCountContainer.js, which sends {eventType: 'connect} message to the server so that this client can be added to broadcast array.
.....
const onConnected = (socket) => {
// Add these lines
socket.send(
JSON.stringify({
eventType: 'connect'
})
);
// This is the original line
socket.send(
JSON.stringify({
eventType: 'clientCount'
})
);
};
......
Please let me know if you have further issues.
Thanks.
I think that the useEffect would have to run all the time, not only when ur url changed or when it is reconnecting.
Try giving this a try: https://stackoverflow.com/a/60161181/10691892
I am using redux and redux-saga in my project. Right now using WebSocket I have a problem calling a FETCH_SUCCESS redux action inside a callback of socket response. I tried making the callback a generator as well but didn't work as well.
function* websocketSaga() {
const socket = new SockJS(`${CONFIG.API_URL}/ws`);
const stomp = Stomp.over(socket);
const token = yield select(selectToken);
stomp.connect(
{
Authorization: `Bearer ${token}`,
},
frame => {
stomp.subscribe('/queue/data', message => {
const response = JSON.parse(message.body);
console.log(response); // here is the proper response, it works
put({
type: FETCH_SUCCESS, // here the FETCH_SUCCESS action is not called
payload: response.dataResponse,
});
});
...
....
}
);
}
Or maybe this WebSocket should be implemented in a completely different way in redux-saga?
You won't be able to use yield put inside a callback function. Stompjs knows nothing about sagas, so it doesn't know what it's supposed to do when given a generator function.
The simplest approach, though not necessarily the best, is to go directly to the redux store in the callback, and dispatch the action without involving redux-saga. For example:
import store from 'wherever you setup your store'
// ...
stomp.subscribe('/queue/data', message => {
const response = JSON.parse(message.body);
store.dispatch({
type: FETCH_SUCCESS,
payload: response.dataResponse,
});
});
If you'd like to use a more redux-saga-y approach, I would recommend wrapping the subscription in an event channel. Event channels take a callback-based API and turn it into something that you can interact with using redux-saga's effects such as take
Here's how you might create the event channel:
import { eventChannel } from 'redux-saga';
function createChannel(token) {
return eventChannel(emitter => {
const socket = new SockJS(`${CONFIG.API_URL}/ws`);
const stomp = Stomp.over(socket);
stomp.connect(
{
Authorization: `Bearer ${token}`,
},
frame => {
stomp.subscribe('/queue/data', message => {
const response = JSON.parse(message.body);
emitter(response); // This is the value which will be made available to your saga
});
}
);
// Returning a cleanup function, to be called if the saga completes or is cancelled
return () => stomp.disconnect();
});
}
And then you'd use it like this:
function* websocketSaga() {
const token = yield select(selectToken);
const channel = createChannel(token);
while (true) {
const response = yield take(channel);
yield put({
type: FETCH_SUCCESS,
payload: response.dataResponse,
});
}
}
Promise should be the perfect fit. Just wrap the callback related code in a promise and resolve it in the callback function. After that use the yield to get the data from the promise. I have modified your code with the Promise below.
function* websocketSaga() {
const socket = new SockJS(`${CONFIG.API_URL}/ws`);
const stomp = Stomp.over(socket);
const token = yield select(selectToken);
const p = new Promise((resolve, reject) => {
stomp.connect(
{
Authorization: `Bearer ${token}`,
},
frame => {
stomp.subscribe('/queue/data', message => {
const response = JSON.parse(message.body);
console.log(response); // here is the proper response, it works
resolve(response); // here resolve the promise, or reject if any error
});
...
....
}
);
});
try {
const response = yield p; // here you will get the resolved data
yield put({
type: FETCH_SUCCESS, // here the FETCH_SUCCESS action is not called
payload: response.dataResponse,
});
} catch (ex) {
// handle error here, with rejected value
}
}
I will give you another way of managing this: create a component connected to redux where you will handle the WS subscription. This component will not render anything to the UI but will be useful for handling redux store interactions.
The main idea is, don't put everything into redux-saga, try and split it into multiple parts to make it easier to maintain.
const socket = new SockJS(`${CONFIG.API_URL}/ws`);
function WSConnection(props) {
const {token, fetchDone} = props;
const [stomp, setStomp] = React.useState();
const onMessage = React.useCallback(message => {
const response = JSON.parse(message.body);
fetchDone(response.dataResponse);
}, [fetchDone]);
const onConnect = React.useCallback(frame => {
const subscription = stomp.subscribe('/queue/data', onMessage);
// cleanup subscription
return () => subscription.unsubscribe();
}, [stomp, onMessage]);
const onError = React.useCallback(error => {
// some error happened, handle it here
}, []);
React.useEffect(() => {
const header = {Authorization: `Bearer ${token}`};
stomp.connect(header, onConnect, onError);
// cleanup function
return () => stomp.disconnect();
}, [stomp])
React.useEffect(() => {
setStomp(Stomp.over(socket));
}, []);
return null;
}
const mapStateToProps = state => ({
... // whatever you need from redux store
});
const mapDispatchToProps = dispatch => ({
... // whatever actions you need to dispatch
});
export default connect(mapStateToProps, mapDispatchToProps)(WSConnection);
You can also take it a step further and extract the stomp logic into another file and reuse it wherever you will need it.
It's not wrong to put everything into redux-saga but it's a nice alternative to handle WS connections inside components connected to redux (and easier to understand to people who are not completely familiar with redux-saga and channels etc).
I have the same stack over the years and only recently I faced websockets over Stomp client.
None of the above solutions doesn't work for me both technically and mentally
Reasons:
I don't like channels with Stomp because the only way to manipulate connections in more surgical way you have to use global state object (for me - it's redux). It doesn't seems right even if you storing only random generated IDS (with unsubscribe function it will be... read more here about store serialization
the way with container another pain in the ... (you know where). Again redux and a lot of under-the-hood functionality used without any reason
another way with promises: again without storing helpful connection info and some DI by using promises inside generators. This narrows down the implementation choice
So:
I need to have connection info (I decided to use state but not in: redux, component state. Singleton state). Stomp doesn't force you to place ID but I do because I want to manage connections by myself
I need one entry point without: promises, iterators and a lot of things that will be pain for future-me. One place to "rule them all" (as I want)
- activate: login
- deactivate: logout
- subscribe: componentDidMount
- unsubscribe: componentWillUnmount
DI by request in one place (passing store.dispatch to constructor only if need it) // main topic of the question
And I wrote this implementation that perfectly works for me:
import SockJS from 'sockjs-client';
import {
Client,
IMessage,
messageCallbackType,
StompHeaders,
} from '#stomp/stompjs';
import { Action, Dispatch } from 'redux';
type ConnectionId = string;
interface IServiceConfig {
url: string;
dispatch?: Dispatch;
}
export default class Stomp {
serviceConfig: IServiceConfig = {
dispatch: null,
url: null,
};
ids: ConnectionId[] = [];
stomp: Client;
constructor(config: IServiceConfig) {
this.serviceConfig = { ...config };
this.stomp = new Client();
this.stomp.webSocketFactory = () => {
return (new SockJS(config.url));
};
}
alreadyInQueue = (id: ConnectionId): boolean => {
return Boolean(this.ids.find(_id => id === _id));
};
subscribeByDispatchAction = (
destination: string,
callback: (message: IMessage) => Action,
headers: StompHeaders & {
id: ConnectionId;
},
): void => {
const alreadyInQueue = this.alreadyInQueue(headers.id);
if (!alreadyInQueue) {
this.stomp.subscribe(
destination,
(message) => {
this.serviceConfig.dispatch(callback(message));
},
headers,
);
this.ids.push(headers.id);
return;
}
console.warn(`Already in queue #${headers.id}`);
};
subscribe = (
destination: string,
callback: messageCallbackType,
headers: StompHeaders & {
id: ConnectionId;
},
): void => {
const alreadyInQueue = this.alreadyInQueue(headers.id);
if (!alreadyInQueue) {
this.stomp.subscribe(
destination,
(message) => callback(message),
headers,
);
this.ids.push(headers.id);
this.logState('subscribe');
return;
}
console.warn(`Failed to subscribe over Socks by #${headers.id}`);
};
unsubscribe = (id: ConnectionId, headers?: StompHeaders): void => {
this.stomp.unsubscribe(id, headers);
this.ids.splice(this.ids.indexOf(id), 1);
};
activate = (): void => {
this.stomp.activate();
};
deactivate = (): void => {
if (this.ids.length === 0) {
this.stomp.deactivate();
return;
}
for (let i = 0; i < this.ids.length; i++) {
this.unsubscribe(this.ids[i]);
}
/**
* it seems like it's overkil but
* for me it works only if i do all
* the things as you see below
* - stomp deactivation
* - closing webSockets manually by using native constant // sockjs-client
* - closing webSockets instance by using returned value fron factory
*/
this.stomp.deactivate();
this.stomp.webSocket.close(
this.stomp.webSocket.CLOSED,
);
this.stomp.webSocketFactory().close();
};
getAllIds = (): readonly ConnectionId[] => {
return this.ids;
};
// debug method
logState = (method: string): void => {
/* eslint-disable */
console.group(`Stomp.${method}`);
console.log('this', this);
console.log('this.ids', this.getAllIds());
console.log('this.stomp', this.stomp);
console.groupEnd();
/* eslint-enable */
};
}
My configuration file
import { store } from '~/index';
import Stomp from '~/modules/_Core/services/Stomp';
import appConfig from '~/modules/Common/services/appConfig';
export const StompService = new Stomp({
dispatch: store?.dispatch,
url: `${appConfig.apiV1}/websocket`,
});
I hope that it will help someone
I'm creating a react app which implements SignalR and so far I have my connection and all the listeners in the component where I need them. The problem is that I have action creators in Redux which just make a request and get the response in order to call my server and send the data to all the other clients. Once the server emits the event to all clients, one of my listeners gets the data and calls an action creator which just dispatches an action to refresh my redux state.
I feel like I'm not using the action creators in the right way because I have one action creator which just makes the request and gets the response to return it and it's not changing the state.
If a had the socket connection in the store, I would just have to call one action creator and the logic to emit or listen to socket event, would be in other place.
This is my component,
// --- component.js ---
state = {
connection: null,
};
async componentDidMount() {
// handles any network exception and show the error message
try {
await this.setupConnection();
} catch (error) {
this.showNetworkError(`Whoops, there was an error with your network connection. Please reload the page`);
}
setupConnection = () => {
let { connection } = this.state;
this.setState({
connection: (connection = new HubConnectionBuilder().withUrl(HUB_URL).build()),
});
/**
* LISTENERS that are called from the server via websockets
*/
connection.on('InsertTodo', data => {
// action creator
this.props.add(data);
});
connection.on('UpdateTodo', data => {
// action creator
this.props.update(data);
});
}
createTodo = async todo => {
const { connection} = this.state;
// action creator
const createdTodo = await this.props.createTodo(todo);
if (createdTodo) {
// the below sentence calls the server to emit/send the todo item to all other clients
// and the listener in the setupConnection function is executed
connection.invoke('EmitTodoCreate', createdTodo);
} else {
// there was a problem creating the todo
}
};
This is the action creator
// --- actionCreators.js ----
// ------------------------
export const add = todo => {
return async (dispatch) => {
dispatch({
type: ADD_TODO,
payload: todo,
});
};
};
export const createTodo = todo => {
return async (dispatch) => {
dispatch({
type: START_REQUEST,
});
const response = await postTodo(todo);
const result = await response.json();
if (response.ok) {
dispatch({
type: SUCCESS_REQUEST,
});
// returns the todo item created in order to be sent to the server via websockets
return result;
}
dispatch({
type: FAILURE_REQUEST,
error: result.error,
});
return null;
};
};
I think the best solution is to implement a Redux middleware. It is easy, and you can use authentication for establishing connection, and you can dispatch action creators based on different messages emitted from SignalR.
Per the Redux FAQ, the right place for websockets and other similar connections is in Redux middleware.
This is my custom middleware that establishes the connection, and registers the handlers. Please note that I only would like to receive data, and not interested in sending data. I use REST APIs to send data to server.
import {
JsonHubProtocol,
HttpTransportType,
HubConnectionBuilder,
LogLevel
} from '#aspnet/signalr'; // version 1.0.4
// action for user authentication and receiving the access_token
import { USER_SIGNED_IN } from '../actions/auth';
const onNotifReceived = res => {
console.log('****** NOTIFICATION ******', res);
};
const startSignalRConnection = connection => connection.start()
.then(() => console.info('SignalR Connected'))
.catch(err => console.error('SignalR Connection Error: ', err));
const signalRMiddleware = ({ getState }) => next => async (action) => {
// register signalR after the user logged in
if (action.type === USER_SIGNED_IN) {
const urlRoot = (window.appConfig || {}).URL_ROOT;
const connectionHub = `${urlRoot}/api/service/hub`;
const protocol = new JsonHubProtocol();
// let transport to fall back to to LongPolling if it needs to
const transport = HttpTransportType.WebSockets | HttpTransportType.LongPolling;
const options = {
transport,
logMessageContent: true,
logger: LogLevel.Trace,
accessTokenFactory: () => action.user.access_token
};
// create the connection instance
const connection = new HubConnectionBuilder()
.withUrl(connectionHub, options)
.withHubProtocol(protocol)
.build();
// event handlers, you can use these to dispatch actions to update your Redux store
connection.on('OperationProgress', onNotifReceived);
connection.on('UploadProgress', onNotifReceived);
connection.on('DownloadProgress', onNotifReceived);
// re-establish the connection if connection dropped
connection.onclose(() => setTimeout(startSignalRConnection(connection), 5000));
startSignalRConnection(connection);
}
return next(action);
};
export default signalRMiddleware;
And inside my store.js file
import signalRMiddleware from '../middlewares/signalRMiddleware';
...
createStore(rootReducer, {}, composeEnhancers(applyMiddleware(signalRMiddleware)));