I implemented my client/server event aggregation library in Vue yesterday and thought it was time for React. I must say React is alot different from Vue, and since I come from Knockout Vue was very easy to adopt. Though after som fiddling I got the hang of it. 😀
Plugins in React
There are several ways of extending React components, I selected the Higher-Order Component (HOC) model.
const withSignalREventAggregator = WrappedComponent => { return class SignalREventAggregatorComponent extends React.Component { constructor(props) { super(props); this.myRef = React.createRef(); this.queuedSubscriptions = []; } componentDidMount() { if (this.queuedSubscriptions.length > 0) { this.queuedSubscriptions.forEach(s => { this.subscribe(s.event, s.handler, s.constraint); }); this.queuedSubscriptions = null; //Only support deferred subscriptions at mount time. } } subscribe = (event, handler, constraint) => { if (!this.myRef.current) { if (!this.queuedSubscriptions) throw "Context scope for subscription could not be resolved"; this.queuedSubscriptions.push({ event, handler, constraint }); return; } signalR.eventAggregator.subscribe(event, handler, this.myRef.current, constraint); } publish = event => { signalR.eventAggregator.publish(event); } componentWillUnmount() { signalR.eventAggregator.unsubscribe(this.myRef.current); } render() { return ( <WrappedComponent {...this.props} subscribe={this.subscribe} publish={this.publish} ref={this.myRef} /> ); } }; };
React.createRef() is used to create a reference to the context of the wrapped component. This way I can marshal the subscription context for the user and unsubscribe the component when its destroyed using the componentWillUnmount event. The wrapped component will call this.props.subscribe from its componentDidMount. The problem is that at this time the wrapper component have not have its reference set and we do not know the reference of the wrapped component. So we have to defer these subscriptions until our own componentDidMount is called and we have the reference to the wrapped component. Other than that we just forward everything to the vanilla library.
Using the plugin
class ReactExample extends React.Component { constructor() { super(); this.state = { events: [] }; } componentDidMount() { this.props.subscribe(MyApp.MyEvent, this.onEvent); } onEvent(e) { this.setState(state => ({ events: state.events.concat(e) })); } } const SignalrReactExample = withSignalREventAggregator(ReactExample);
Above I have declared a React component and extended it with the withSignalREventAggregator function. From the componentDidMount method I call subscribe which will hook up the server side event and the onEvent method will fire whenever a server side event is fired.