Skip to content

Omhet/redux-signalr

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

49 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Redux middleware for SignalR (ASP.NET Core)

NPM Version NPM License

Installation

npm install redux-signalr

or

yarn add redux-signalr

Usage

NOTE: 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;