Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Where to put SignalR hub in React/Redux app?

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 item population using SignalR.

The way I wanted to do this was to have SignalR send item update messages both to initialise the starting population when you connect to the server hub, as well as updates via the same message type as time goes on. I would have a function that takes a SignalR message and converts it to a Redux action and dispatches to Redux store, which would then use the action to update the state and then the UI.

So the idea is

1) Connect to SignalR server hub, with client handler function set up for ItemUpdate messages

2) When server receives Connect() from the client, it sends ItemUpdate messages for all current items in the population

3) The client receives these messages from SignalR, transforms to actions and dispatches to the Redux store

4) Redux updates the store based on the new item information and the UI displays it

5) Server realises an item has been added or updated and sends a new ItemUpdate message for the update to the client

6) Repeat

However I am not sure of exactly where I should keep the hub singleton as this seems counter to React/Redux design. Can someone advise on the best way to do this?

My main app

import * as React from 'react';
import * as ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import App from './App';
import './index.css';
import registerServiceWorker from './registerServiceWorker';
import 'rxjs';
import store from './store/index';

ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById('root') as HTMLElement
);

registerServiceWorker();

My store creation file

import { createStore, applyMiddleware } from 'redux';
import rootReducer from '../reducers/index';
import signalRMiddleware from '../signalr/middleware';

const store = createStore(rootReducer, applyMiddleware(signalRMiddleware));
export default store;

My middleware for outbound SignalR messages to the server (commented out as I do not have access to the hub object I need for this to work

export default function signalRMiddleware(store: any) {
    return (next: any) => (action: any) => {
        if (action.signalR) {
            switch (action.type) {
                default:
                    {
                        //const myCurrentState = store.getState().objectWithinState;
                        //_hub.server.methodOnTheServer2(action.type, myCurrentState);
                    }
            }
        }
        return next(action);
    }
}

Now for the incoming messages... this is the shell of a signalR start function I got from an online example - not yet implemented as I do not have the hub and connection yet and not sure where this should go

export function signalRStart(store: any, callback: Function) {
    _hub = $.connection.myHubName;

    _hub.client.firstClientFunction = (p1: any) => {
        store.dispatch({ type: "SERVER_CALLED_ME", a: p1 });
    }

    _hub.client.secondClientFunction = (p1: string, p2: string) => {
            store.dispatch({ type: "SERVER_CALLED_ME_2", value: p1 + p2 });
        }
    }

    $.connection.hub.start(() => callback());
}

And this is the example given on the website I found the code on to tie it all together, however I do not see how this can integrate with React/Redux as in my main Index page, I have to pass the created store to the Provider component and so I cannot put the hub creation below this, as you need the hub for the signalr middleware component which is passed into the store creation

let _hub;

let store = createStore(
  todoApp,
  // applyMiddleware() tells createStore() how to handle middleware
  applyMiddleware(signalRMiddleware)
)

// Make sure signalr is connected
signalRStart(store, () => {
    render((...),
    document.getElementById("app-container"));
});

Can someone advise on the best way to integrate SignalR into my React/Redux app?

like image 218
NZJames Avatar asked May 31 '18 13:05

NZJames


1 Answers

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)
like image 135
xeiton Avatar answered Oct 27 '22 02:10

xeiton