I\'m designing a React website using Redux as the state store, which is primarily to display the current population of items to the user, using live updates to update the it
Per the Redux FAQ, the right place for websockets and other similar connections is in Redux middleware.
Here is a list of existing websocket middle-wares. You can look at the source code of a couple of them and very easily get an idea of how to implement your own custom middle-ware:
A middleware can dispatch actions. Here's an example of what a socket middleware might look like, and dispatching an action that it listens for:
const createMySocketMiddleware = (url) => {
return storeAPI => {
let socket = createMyWebsocket(url);
socket.on("message", (message) => {
storeAPI.dispatch({
type : "SOCKET_MESSAGE_RECEIVED",
payload : message
});
});
return next => action => {
if(action.type == "SEND_WEBSOCKET_MESSAGE") {
socket.send(action.payload);
return;
}
return next(action);
}
}
}
You need to apply this middleware to your redux store
let store = createStore(
some_reducer,
applyMiddleware(createMySocketMiddleware)
)
Later, in your app. This is an action creator
const sendSocketMessage = message => ({
type : "SEND_WEBSOCKET_MESSAGE",
payload : message
}
Add a button in your component to dispatch an action via websockets
class MyComponent extends React.Component {
handleClick = () => {
this.props.sendSocketMessage("This goes to the server");
}
}
export default connect(null, {sendSocketMessage})(MyComponent)
For people that may find this thread in future.
This is my custom middleware that only establishes the connection, and registers the handlers. Please note that I only would like to receive data, and not interested in sending data.
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)));
UPDATE June 2020 This is how we do it now with the new package @microsoft/signalr https://stackoverflow.com/a/62162742/10232269 This is not using the middleware method. We use Redux, but you don't have to use Redux to utilize this method.