Skip to content

Instantly share code, notes, and snippets.

@markerikson
Created June 28, 2018 00:37
Show Gist options
  • Save markerikson/3df1cf5abbac57820a20059287b4be58 to your computer and use it in GitHub Desktop.
Save markerikson/3df1cf5abbac57820a20059287b4be58 to your computer and use it in GitHub Desktop.
Redux socket middleware example usage
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);
}
}
}
// later, in your app
function sendSocketMessage(message) {
return {
type : "SEND_WEBSOCKET_MESSAGE",
payload : message
}
}
class MyComponent extends React.Component {
handleClick = () => {
this.props.sendSocketMessage("This goes to the server");
}
}
export default connect(null, {sendSocketMessage})(MyComponent)
@n-ii-ma
Copy link

n-ii-ma commented Nov 2, 2022

@markerikson: Thank you for your response Mark. Well, I've used your example here and on Stackoverflow as a base to configure the Socket middleware with Socket IO and Laravel Echo in this way and just as the docs recommends I use .concat to append the middleware to the store:

(Each section is in its own file)

// Actions
// Get data
export const getData = createAction('socket/getData', data => {
  return {
    payload: {
      data,
      id: nanoid(),
      createdAt: new Date().toISOString(),
    },
  };
});

// Send data
export const sendData = createAction('socket/sendData', data => {
  return {
    payload: {
      data,
      id: nanoid(),
      createdAt: new Date().toISOString(),
    },
  };
});

// Disconnect
export const disconnect = createAction('socket/disconnect');

// Socket middleware
export const createSocketMiddleware = url => {
  let socket;
  let echo;

  return storeAPI => next => action => {
    switch (action.type) {
      // Connect after user is authenticated
      case 'user/login/fulfilled': {
        socket = socketIOClient(`${url}:3010/socket`);

        // Create a new Echo class instance
        echo = new Echo({
          host: `${url}:6001`,
          broadcaster: 'socket.io',
          client: socketIOClient,
          auth: {
            headers: {
              Authorization: axios.defaults.headers['Authorization'],
            },
          },
        });

        // Connect and listen
        echo.private('transport.1').listen('.order-pending', ev => {
          storeAPI.dispatch(getData(ev));
        });
        break;
      }

      // Send data
      case 'socket/sendData': {
        socket.emit('order-pending', action.payload);
        break;
      }

      // Disconnect
      case 'socket/disconnect': {
        echo.disconnect();
        break;
      }
    }
    return next(action);
  };
};

// Reducer
const socketReducer = createReducer(initialState, builder => {
  builder
    .addCase(getData, (state, action) => {
      state.data = action.payload.data;
    })
    .addCase(disconnect, (state, action) => {
      return initialState;
    });
});

// Store
export const store = configureStore({
  reducer: persistedReducer,
  middleware: getDefaultMiddleware =>
    getDefaultMiddleware({
      serializableCheck: {
        ignoredActions: [FLUSH, REHYDRATE, PAUSE, PERSIST, PURGE, REGISTER],
      },
    }).concat(createSocketMiddleware(url)),
  devTools: process.env.NODE_ENV === 'development' && true,
});

Everything is working as expected yet I myself am a bit cautious regarding this method due to the lack of adequate examples and am wondering if it indeed adheres to the Redux standards.

One more question I was wondering about is why is redux requiring the WebSocket connection to be as a middleware and how is RTK Query handling this issue with its Streaming Updates that plain Redux Toolkit is unable to do.

@markerikson
Copy link
Author

@n-ii-ma looks fine at first glance.

You might want to look at the FAQ entry that describes why we normally recommend that websocket-type connections should go into a middleware:

https://redux.js.org/faq/code-structure#where-should-websockets-and-other-persistent-connections-live

@n-ii-ma
Copy link

n-ii-ma commented Nov 10, 2022

@markerikson Many thanks Mike!

Upon further consideration and after a conversation with Lenz, I decided to use the listenerMiddleware, yet I'm still trying to figure out how to stop a particular listener

@markerikson
Copy link
Author

@n-ii-ma probably best to ask over in #redux in Reactiflux, but typically you'd dispatch an action and have the listener do an await take(thatAction) and then cancel itself / return.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment