4

So basically I got this socket, that is working correctly sending me 'new order' message.

I'm using redux, and i want to dispatch an action, than a reducer would get it and my store would be updated. but this code doesn't do anything!

socket.on('new order', (order) => {    
    return (dispatch) => {
      dispatch(socketNewOrder(order));
    }
});

And here is my action, which is located at the same file:

export const socketNewOrder = (order) => {
  return {
    type: 'SOCKET_NEW_ORDER',
    payload: order
  }
}

I also tried to call my action like this:

socket.on('new order', (order) => {    
    socketNewOrder(order));        
});

It called the action indeed, but the action wasn't 'heard' by my reducer! :(

I heard something about using middlewares, but i just can't figure out how to do it.

Could anyone explain me how to use middlewares for dispatching actions as i receive socket messages, and why my code doesn't work? Thanks and sorry for newbie questio

Amanda Siqueira
  • 75
  • 1
  • 2
  • 7

3 Answers3

5

This code should work for you:

export const socketNewOrder = (order) => {
  return {
    type: 'SOCKET_NEW_ORDER',
    payload: order
  }
}

const handlerFunc = (dispatch) => (order) => {
    dispatch(socketNewOrder(order));
  }
});

socket.on('event', handlerFunc(dispatch));
// make sure the stores dispatch method is within scope

Explanation

Your event handler function were correctly broken down into a series of functions. However these functions were in the wrong order.

socket.on('new order', (order) => {    
    return (dispatch) => {
      dispatch(socketNewOrder(order));
    }
});

This is the correct order of the series functions that make up your eventHandler function:

socket.on('new order', (dispatch) => {    
    return (order) => {
      dispatch(socketNewOrder(order));
    }
});

Binding a handler function to a socket in the normal way would look like.

socket.on('event', handlerFunc)

So the handler function would only be called when the event is triggered.

This won't work for us if we need to pass dispatch to the handlerFunc when it is bound before handlerFunc is called when the event is triggered.

However we can solve this through the use of a functional programming technique called currying which allows us to break the event handler handler function into a series of functions that can be called at progressively later points in time.

Currying is when you break down a function that takes multiple arguments into a series of functions that take part of the arguments.

There are two important points in time for socket events.

  1. The handler function is bound to the socket instance

  2. The handler function is called

We have access to the Redux store's dispatch method at timepoint one but not at timepoint two. Currying allows us to 'store' the dispatch method for timepoint two.

So what we can do is call a function with dispatch that returns our handlerFunction.

function handlerFunc(order){
  dispatch(socketNewOrder(order));
}

function passDispatch(dispatch){
  return handlerFunc
};

socket.on('event', passDispatch(dispatch));

So although this may look odd it results in exactly the same thing as the first example. Through currying although the event handler will be called at a later point in time we will still be able to dispatch actions as we have access to the dispatch variable.

We could use middleware to alleviate the repetition of currying our handler functions each time they are bound.

therewillbecode
  • 7,090
  • 4
  • 35
  • 42
3

The point is that you need to have access to dispatch in your socket event listener. Using a middleware to create actions from external event sources is a valid pattern.

const socketMiddleware = (store) => {
  // We have access to store, add socket listeners
  socket.on('new order', (order) => {    
    store.dispatch(socketNewOrder(order));
  });  

  // Actual middleware implementation just passes all
  // actions through without touching them
  return (next) => (action) => {
    next(action);
  }
}
OlliM
  • 7,023
  • 1
  • 36
  • 47
  • This implementation of middleware hardcodes the event names. Also where is the socket instance being referenced from. Would it be better to pass in the socket instance as an argument to the middleware call before the store arg? – therewillbecode May 10 '17 at 09:52
0

Example application maybe helpful. Update callback list with socket

SocketService.js

import io from 'socket.io-client';
import React from 'react';
import { authStore } from "../../store/mobx/AuthStore"

let _socket;
let socketConnectionListeners = [];

class SocketService {

    get socket() { return _socket; }

    constructor() {
        this.connect();
    }

    connect = async () => {
        try {
            const token = await authStore.getTokenAsync();
            if (token) {
                _socket = io("http://localhost:8181", { query: `auth_token=${token}` });
                await Promise.all([
                    registerSocketEvents(this.socket),
                    registerCommonEvents(this.socket)
                ]);
            }
        } catch (error) {
            console.log('SocketService/connect/catch:', error);
        }
    }

    registerSocketConnectionCallBack = (callback) => {
        socketConnectionListeners.push(callback);
        return {
            remove: () => socketConnectionListeners = socketConnectionListeners.filter(c => c != callback)
        }
    }

    disconnect = () => {
        if (this.socket) {
            this.socket.emit('__disconnect_');
        }
    }
}

const registerSocketEvents = async (socket, path) => {
    socket.on('connect', () => console.log('socket connected'));
    socket.on('disconnect', () => console.log('socket disconnected'));
    socket.on('error', (error) => console.log('socket error', error));
}

const registerCommonEvents = async (socket) => {
    socket.on("updateCallbackList", (...data) => {
        console.log('updateCallbackList called', data);
    });
}

const SocketService = new SocketService();
export default SocketService;

CallbackList Functional Component

import React, { Fragment, useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { fetchCallbacks } from '../../store/redux-toolkit/callback/callbackActions';
import socketService from '../../common/services/SocketService';

const CallbackList = (props) => {

    const dispatch = useDispatch();
    const callbackState = useSelector((state) => state.callback);
    const { loading, list } = callbackState;

    useEffect(() => {
        socketService.socket.on("updateCallbackList", (...data) => {
            dispatch(fetchCallbacks());
        });
        dispatch(fetchCallbacks());
    }, []);

    return (<Fragment></Fragment>);
}

export default CallbackList;
Nijat Aliyev
  • 558
  • 6
  • 15