Redux middleware for SignalR (ASP.NET Core)
Installation
npm install redux-signalr
or
Usage
NOTE: You don't need to install @microsoft/signalr as it's already included in this package for convenience and exports all the code from @microsoft/signalr.
Also, apart of SignalR invoke method, redux-signalr gives you an access to Redux state and dispatch in actions, so you don't need to use redux-thunk and redux-signalr simultaneously as the latter already does the same job.
First, configuration
Build a connection object
const connection = new HubConnectionBuilder ( )
. configureLogging ( LogLevel . Debug )
. withUrl ( ' https://0.0.0.0:5001/testHub ' , {
skipNegotiation : true ,
transport : HttpTransportType . WebSockets ,
} )
. build ( ) ;
Register callbacks
In the callbacks you have an access to redux dispatch and getState and signalr invoke methods.
const callbacks = withCallbacks ( )
. add ( ' ReceiveMessage ' , ( msg : string ) => ( dispatch ) => {
dispatch ( setText ( msg ) ) ;
} )
. add ( ' ReceiveRandomNumber ' , ( num : number ) => ( dispatch , getState , invoke ) => {
const { example } = getState ( ) ;
dispatch ( setRandomNumber ( num ) ) ;
invoke ( ' SendMessage ' , txt + example . text )
} )
Create middleware with the callbcaks and connection object
export const signal = signalMiddleware ( {
callbacks ,
connection ,
} ) ;
Second, apply the configured middleware
import { signal } from ' ./helpers/withSignalR ' ;
export default function configureStore ( preloadedState ? : RootState ) {
return createStore (
rootReducer ,
preloadedState ,
applyMiddleware ( signal )
) ;
}
Third, write action functions as you would do with thunk, but now it has the third parameter - invoke (from signalR) to call server methods
export const sendMessage = ( txt : string ) : Action => ( dispatch , getState , invoke ) => {
invoke ( ' SendMessage ' , txt )
} ;
Fourth (only for TS), add custom types
import { rootReducer } from ' ./rootReducer ' ;
import { AnyAction } from ' redux ' ;
import { SignalAction , SignalDispatch } from ' redux-signalr ' ;
export type RootState = ReturnType < typeof rootReducer > ;
export type Action < ReturnValue = void > = SignalAction <
ReturnValue ,
RootState ,
AnyAction
> ;
export type Dispatch < Action extends AnyAction = AnyAction > = SignalDispatch <
RootState ,
Action
> ;
Use those Dispatch and RootState types in callbacks, this way you will have correct typings for dispatch and getState methods in your callbacks
const callbacks = withCallbacks < Dispatch , RootState > ( )
. add ( ' CallbackName ' , ( ) => ( dispatch , getState , invoke ) => { }
Additional features
Don't start a connection immediately
Create signalMiddleware with shouldConnectionStartImmediately set to false.
const signal = signalMiddleware ( {
callbacks ,
connection ,
shouldConnectionStartImmediately : false
} ) ;
Then, import the 'connection' in the place you want and start it if it's not already.
Here is an example with a simple Button container:
import { connection } from " ../redux/helpers/createSignalMiddleware " ;
const StartConnectionButton : FunctionComponent = ( ) => {
const handleClick = useCallback ( ( ) => {
if ( connection . state !== HubConnectionState . Connected ) {
connection
. start ( )
. then ( ( ) => console . log ( " Connection started " ) )
. catch ( ( err ) => console . error ( err . toString ( ) ) ) ;
}
} , [ ] ) ;
return < Button onClick = { handleClick } > Start Connection < / Button > ;
} ;
export default StartConnectionButton ;